relevant tag icon
Chainhop DEX Aggregator
copy icon
链求君
• version 1.0.0
Aggregator
Deployable

Chainhop DEX Aggregator

Contracts for the Chainhop Interchain DEX Aggregator

*Visit desktop site to download or deploy

Version

1.0.0

Creator

链求君

Recent Use

🍞 0xE856 downloaded

Last Publish

3/12/2023
Any contract you deploy is yours.
Fully owned and controlled by your wallet.
Documentation
Source Code
ExecutionNode.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "./lib/Types.sol"; import "./lib/MessageReceiver.sol"; import "./lib/Pauser.sol"; import "./lib/NativeWrap.sol"; import "./lib/Bytes.sol"; import "./interfaces/IBridgeAdapter.sol"; import "./interfaces/ICodec.sol"; import "./interfaces/IExecutionNodeEvents.sol"; import "./interfaces/IWETH.sol"; import "./interfaces/IMessageBus.sol"; import "./registries/BridgeRegistry.sol"; import "./registries/DexRegistry.sol"; import "./registries/RemoteExecutionNodeRegistry.sol"; import "./registries/FeeVaultRegistry.sol"; import "./SigVerifier.sol"; import "./Pocket.sol"; /** * @author Chainhop Dex Team * @author Padoriku * @title a route execution contract * @notice * a few key concepts about how the chain of execution works: * - a "swap-bridge execution combo" (Types.ExecutionInfo) is a node in the execution chain * - a node be swap-only, bridge-only, or swap-bridge * - a message is an edge in the execution chain, it carries the remaining swap-bridge combos to the next node * - execute() executes a swap-bridge combo and determines if the current node is the final one by looking at Types.DestinationInfo * - executeMessage() is called on the intermediate nodes by chainhop's executor. it simply calls execute() to advance the execution chain * - a "pocket" is a counterfactual contract of which the address is determined at quote-time by chainhop's pathfinder server with using * the id as salt. the actual pocket contract deployment is done at execution time by the the ExecutionNode on that chain */ contract ExecutionNode is IExecutionNodeEvents, MessageReceiver, DexRegistry, BridgeRegistry, SigVerifier, FeeVaultRegistry, NativeWrap, ReentrancyGuard, Pauser, RemoteExecutionNodeRegistry { using SafeERC20 for IERC20; using ECDSA for bytes32; using Bytes for bytes; constructor( bool _testMode, address _messageBus, address _nativeWrap ) MessageReceiver(_testMode, _messageBus) NativeWrap(_nativeWrap) { _disableInitializers(); } // init() can only be called once during the first deployment of the proxy contract. // any subsequent changes to the proxy contract's state must be done through their respective set methods via owner key. function init( bool _testMode, address _messageBus, address _nativeWrap, address _signer, address _feeVault, address[] memory _dexList, string[] memory _funcs, address[] memory _codecs, string[] memory _bridgeProviders, address[] memory _bridgeAdapters ) external initializer { initOwner(); initMessageReceiver(_testMode, _messageBus); initDexRegistry(_dexList, _funcs, _codecs); initBridgeRegistry(_bridgeProviders, _bridgeAdapters); initFeeVaultRegistry(_feeVault); initSigVerifier(_signer); initNativeWrap(_nativeWrap); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Core * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * @notice executes a swap-bridge combo and relays the next swap-bridge combo to the next chain (if any) * @param _execs contains info that tells this contract how to collect a part of the bridge token * received as fee and how to swap can be omitted on the source chain if there is no swaps to execute * @param _src info that is processed on the source chain. only required on the source chain and should not be populated on subsequent hops * @param _dst the receiving info of the entire operation */ function srcExecute( Types.ExecutionInfo[] memory _execs, Types.SourceInfo memory _src, Types.DestinationInfo memory _dst ) external payable whenNotPaused nonReentrant { require(_execs.length > 0, "nop"); require(_src.amountIn > 0, "0 amount"); require(_dst.receiver != address(0), "0 receiver"); bytes32 id = _computeId(msg.sender, _dst.receiver, _src.nonce); Types.ExecutionInfo memory exec = _execs[0]; if (_execs.length > 1) { _verify(_execs, _src); } (uint256 amountIn, address tokenIn) = _pullFundFromSender(_src); require(amountIn > 0, "amount must > 0"); // process swap if any uint256 nextAmount = amountIn; address nextToken = tokenIn; if (exec.swap.dex != address(0)) { bool success = true; (success, nextAmount, nextToken) = _executeSwap(exec.swap, amountIn, tokenIn); require(success, "swap fail"); } _processNextStep(id, _execs, _dst, nextToken, nextAmount); } /** * @notice called by cBridge MessageBus. processes the execution info and carry on the executions * @param _message the message that contains the remaining swap-bridge combos to be executed * @return executionStatus always success if no reverts to let the MessageBus know that the message is processed */ function executeMessage( address _sender, uint64 _srcChainId, bytes memory _message, address // _executor ) external payable override onlyMessageBus onlyRemoteExecutionNode(_srcChainId, _sender) whenNotPaused nonReentrant returns (ExecutionStatus) { Types.Message memory m = abi.decode((_message), (Types.Message)); require(m.execs.length > 0, "nop"); uint256 remainingValue = msg.value; Types.ExecutionInfo memory exec = m.execs[0]; (uint256 amountIn, address tokenIn) = _pullFundFromPocket(m.id, exec); // if amountIn is 0 after deducting fee, this contract keeps all amountIn as fee and // ends the execution if (amountIn == 0) { emit StepExecuted(m.id, 0, tokenIn); return _refundValueAndDone(remainingValue); } // refund immediately if receives bridge out fallback token if (tokenIn == exec.bridgeOutFallbackToken) { _sendToken(tokenIn, amountIn, m.dst.receiver, false); emit StepExecuted(m.id, amountIn, tokenIn); return _refundValueAndDone(remainingValue); } // process swap if any uint256 nextAmount = amountIn; address nextToken = tokenIn; if (exec.swap.dex != address(0)) { bool success = true; (success, nextAmount, nextToken) = _executeSwap(exec.swap, amountIn, tokenIn); // refund immediately if swap fails if (!success) { _sendToken(tokenIn, amountIn, m.dst.receiver, false); emit StepExecuted(m.id, amountIn, tokenIn); return _refundValueAndDone(remainingValue); } } uint256 consumedValue = _processNextStep(m.id, m.execs, m.dst, nextToken, nextAmount); remainingValue -= consumedValue; return _refundValueAndDone(remainingValue); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Periphery * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // the receiver of a swap is entitled to all the funds in the pocket. as long as someone can prove // that they are the receiver of a swap, they can always recreate the pocket contract and claim the // funds inside. function claimPocketFund( address _srcSender, address _dstReceiver, uint64 _nonce, address _token ) external whenNotPaused nonReentrant { require(msg.sender == _dstReceiver, "only receiver can claim"); // id ensures that only the designated receiver of a swap can claim funds from the designated pocket of a swap bytes32 id = _computeId(_srcSender, _dstReceiver, _nonce); Pocket pocket = new Pocket{salt: id}(); uint256 erc20Amount = IERC20(_token).balanceOf(address(pocket)); uint256 nativeAmount = address(pocket).balance; require(erc20Amount > 0 || nativeAmount > 0, "pocket is empty"); // this claims both _token and native _claimPocketERC20(pocket, _token, erc20Amount); if (erc20Amount > 0) { IERC20(_token).safeTransfer(_dstReceiver, erc20Amount); } if (nativeAmount > 0) { (bool ok, ) = _dstReceiver.call{value: nativeAmount, gas: 50000}(""); require(ok, "failed to send native"); } emit PocketFundClaimed(_dstReceiver, erc20Amount, _token, nativeAmount); } /** * @notice allows the owner to extract stuck funds from this contract and sent to _receiver * @dev since bridged funds are sent to the pocket contract, and fees are sent to the fee vault, * normally there should be no residue funds in this contract. but in case someone mistakenly * send tokens directly to this contract, this function can be used to access these funds. * @param _token the token to extract, use address(0) for native token */ function resecueFund(address _token) external onlyOwner { if (_token == address(0)) { (bool ok, ) = owner().call{value: address(this).balance}(""); require(ok, "send native failed"); } else { IERC20(_token).safeTransfer(owner(), IERC20(_token).balanceOf(address(this))); } } /** * @notice sets allowance to 0 for a token and spender * @dev normally, all allowances are revoked from a dex after swapping. this function exists mainly to * handle a historical issue where allowance is stuck at a non-zero value at a dex * @param _token for which token to revoke allowance * @param _spender for which spender to revoke allowance */ function revokeAllowance(address _token, address _spender) external onlyOwner { IERC20(_token).safeApprove(_spender, 0); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Misc * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // encoding src sender into the id prevents the scenario where different senders can send funds to the the same receiver // causing the swap behavior to be non-deterministic. e.g. if src sender is not in id generation, an attacker can send // send a modified swap data as soon as they see the victim executes on the src chain. since the processing of messages // is asynchronous, the hacker's message can be executed first, accessing the fund inside the victim's pocket and // swapping it in some unfavorable ways. // // note that if the original tx sender is a contract, the integrator MUST ensure that they maintain a unique nonce so // that the same sender/receiver/nonce combo cannot be used twice. otherwise, the above attack is possible via the // integrator's contract. TODO: maybe add the nonce maintenance in this contract. function _computeId(address _srcSender, address _dstReceiver, uint64 _nonce) private pure returns (bytes32) { // the main purpose of this id is to uniquely identify a user-swap. return keccak256(abi.encodePacked(_srcSender, _dstReceiver, _nonce)); } function _processNextStep( bytes32 _id, Types.ExecutionInfo[] memory _execs, Types.DestinationInfo memory _dst, address _nextToken, uint256 _nextAmount ) private returns (uint256 consumedValue) { Types.ExecutionInfo memory exec = _execs[0]; _execs = _removeFirst(_execs); // pay receiver if there is no more swaps or bridges if (_execs.length == 0 && exec.bridge.toChainId == 0) { _sendToken(_nextToken, _nextAmount, _dst.receiver, _dst.nativeOut); emit StepExecuted(_id, _nextAmount, _nextToken); return 0; } // funds are bridged directly to the receiver if there are no subsequent executions on the destination chain. // otherwise, it's sent to a "pocket" contract addr to temporarily hold the fund before it is used for swapping. address bridgeOutReceiver = _dst.receiver; // if there are more execution steps left, pack them and send to the next chain if (_execs.length > 0) { address remote = remotes[exec.bridge.toChainId]; require(remote != address(0), "remote not found"); bridgeOutReceiver = _getPocketAddr(_id, remote); bytes memory message = abi.encode(Types.Message({id: _id, execs: _execs, dst: _dst})); uint256 msgFee = IMessageBus(messageBus).calcFee(message); IMessageBus(messageBus).sendMessage{value: msgFee}(remote, exec.bridge.toChainId, message); consumedValue += msgFee; } _bridgeSend(exec.bridge, bridgeOutReceiver, _nextToken, _nextAmount); consumedValue += exec.bridge.nativeFee; emit StepExecuted(_id, _nextAmount, _nextToken); } function _refundValueAndDone(uint256 _remainingValue) private returns (ExecutionStatus status) { // chainhop executor would always send a set amount of native token when calling messagebus's executeMessage(). // these tokens cover the fee introduced by chaining another message when there are more bridging. // refunding the unspent native tokens back to the executor if (_remainingValue > 0) { // TODO, use the _executor param passed in from executeMessage for the refund receiver (bool ok, ) = tx.origin.call{value: _remainingValue, gas: 50000}(""); require(ok, "failed to refund remaining native token"); } return ExecutionStatus.Success; } function _pullFundFromSender(Types.SourceInfo memory _src) private returns (uint256 amount, address token) { if (_src.nativeIn) { require(_src.tokenIn == nativeWrap, "tokenIn not nativeWrap"); require(msg.value >= _src.amountIn, "insufficient native amount"); IWETH(nativeWrap).deposit{value: _src.amountIn}(); } else { IERC20(_src.tokenIn).safeTransferFrom(msg.sender, address(this), _src.amountIn); } return (_src.amountIn, _src.tokenIn); } function _pullFundFromPocket( bytes32 _id, Types.ExecutionInfo memory _exec ) private returns (uint256 amount, address token) { Pocket pocket = new Pocket{salt: _id}(); uint256 fallbackAmount; if (_exec.bridgeOutFallbackToken != address(0)) { fallbackAmount = IERC20(_exec.bridgeOutFallbackToken).balanceOf(address(pocket)); // e.g. hToken/anyToken } uint256 erc20Amount = IERC20(_exec.bridgeOutToken).balanceOf(address(pocket)); uint256 nativeAmount = address(pocket).balance; // if the pocket does not have bridgeOutMin, we consider the transfer not arrived yet. in // this case we tell the msgbus to revert the outter tx using the MSG::ABORT: prefix and // our executor will retry sending this tx later. // // this bridgeOutMin is also a counter-measure to a DoS attack vector. if we assume the bridge // funds have arrived once we see a balance in the pocket, an attacker can deposit a small // amount of fund into the pocket and confuse this contract that the bridged fund has arrived. // this triggers the refund logic branch and thus denying the dst swap for the victim. // bridgeOutMin is determined by the server before sending out the transfer. // bridgeOutMin = R * bridgeAmountIn where R is an arbitrary ratio that we feel effective in // raising the attacker's attack cost. if (fallbackAmount > _exec.bridgeOutFallbackMin) { _claimPocketERC20(pocket, _exec.bridgeOutFallbackToken, fallbackAmount); amount = _deductFee(fallbackAmount, _exec.feeInBridgeOutFallbackToken, _exec.bridgeOutFallbackToken); token = _exec.bridgeOutFallbackToken; } else if (erc20Amount > _exec.bridgeOutMin) { _claimPocketERC20(pocket, _exec.bridgeOutToken, erc20Amount); amount = _deductFee(erc20Amount, _exec.feeInBridgeOutToken, _exec.bridgeOutToken); token = _exec.bridgeOutToken; } else if (nativeAmount > _exec.bridgeOutMin) { // no need to check before/after balance here since selfdestruct is guaranteed to // send all native tokens from the pocket to this contract. pocket.claim(address(0), 0); require(_exec.bridgeOutToken == nativeWrap, "bridgeOutToken not nativeWrap"); amount = _deductFee(nativeAmount, _exec.feeInBridgeOutToken, _exec.bridgeOutToken); IWETH(_exec.bridgeOutToken).deposit{value: amount}(); token = _exec.bridgeOutToken; } else { revert("MSG::ABORT:pocket is empty"); } } // since the call result of the transfer function in the pocket contract is not checked, we check // the before and after balance of this contract to ensure that the amount is indeed received. function _claimPocketERC20(Pocket _pocket, address _token, uint256 _amount) private { uint256 balBefore = IERC20(_token).balanceOf(address(this)); _pocket.claim(_token, _amount); uint256 balAfter = IERC20(_token).balanceOf(address(this)); require(balAfter - balBefore >= _amount, "insufficient fund claimed"); } function _getPocketAddr(bytes32 _salt, address _deployer) private pure returns (address) { // how to predict a create2 address: // https://docs.soliditylang.org/en/v0.8.17/control-structures.html?highlight=create2#salted-contract-creations-create2 bytes32 hash = keccak256( abi.encodePacked(bytes1(0xff), _deployer, _salt, keccak256(type(Pocket).creationCode)) ); return address(uint160(uint256(hash))); } function _deductFee(uint256 _amount, uint256 _fee, address _token) private returns (uint256 amount) { uint256 fee; // handle the case where amount received is not enough to pay fee if (_amount > _fee) { amount = _amount - _fee; fee = _fee; } else { fee = _amount; } if (_token == nativeWrap) { // TODO if the _executor param passed in from executeMessage is our executor, send fee // to fee vault. otherwise, send fee to the _executor address (bool ok, ) = feeVault.call{value: fee}(""); require(ok, "send native failed"); } else { IERC20(_token).safeTransfer(feeVault, fee); } } function _bridgeSend(Types.BridgeInfo memory _bridge, address _receiver, address _token, uint256 _amount) private { IBridgeAdapter bridge = bridges[keccak256(bytes(_bridge.bridgeProvider))]; IERC20(_token).safeIncreaseAllowance(address(bridge), _amount); bridge.bridge{value: _bridge.nativeFee}(_bridge.toChainId, _receiver, _amount, _token, _bridge.bridgeParams); } function _executeSwap( ICodec.SwapDescription memory _swap, uint256 _amountIn, address _tokenIn ) private returns (bool ok, uint256 amountOut, address tokenOut) { if (_swap.dex == address(0)) { // nop swap return (true, _amountIn, _tokenIn); } bytes4 selector = bytes4(_swap.data); ICodec codec = getCodec(_swap.dex, selector); address tokenIn; (, tokenIn, tokenOut) = codec.decodeCalldata(_swap); require(tokenIn == _tokenIn, "swap info mismatch"); bytes memory data = codec.encodeCalldataWithOverride(_swap.data, _amountIn, address(this)); IERC20(tokenIn).safeApprove(_swap.dex, _amountIn); uint256 balBefore = IERC20(tokenOut).balanceOf(address(this)); (bool success, ) = _swap.dex.call(data); // always revoke all allowance after swapping to: // 1. prevent malicious dex to pull funds from this contract later // 2. workaround some token's impl of approve() that requires current allowance == 0 if (IERC20(tokenIn).allowance(address(this), _swap.dex) > 0) { IERC20(tokenIn).safeApprove(_swap.dex, 0); } if (!success) { return (false, 0, tokenOut); } uint256 balAfter = IERC20(tokenOut).balanceOf(address(this)); return (true, balAfter - balBefore, tokenOut); } function _sendToken(address _token, uint256 _amount, address _receiver, bool _nativeOut) private { if (_nativeOut) { require(_token == nativeWrap, "token is not nativeWrap"); IWETH(nativeWrap).withdraw(_amount); (bool sent, ) = _receiver.call{value: _amount, gas: 50000}(""); require(sent, "send fail"); } else { IERC20(_token).safeTransfer(_receiver, _amount); } } function _removeFirst( Types.ExecutionInfo[] memory _execs ) private pure returns (Types.ExecutionInfo[] memory rest) { require(_execs.length > 0, "empty execs"); rest = new Types.ExecutionInfo[](_execs.length - 1); for (uint256 i = 1; i < _execs.length; i++) { rest[i - 1] = _execs[i]; } } function _verify(Types.ExecutionInfo[] memory _execs, Types.SourceInfo memory _src) private view { require(_src.deadline > block.timestamp, "deadline exceeded"); bytes memory data = abi.encodePacked( "chainhop quote", uint64(block.chainid), _src.amountIn, _src.tokenIn, _src.deadline ); for (uint256 i = 1; i < _execs.length; i++) { Types.ExecutionInfo memory e = _execs[i]; Types.BridgeInfo memory prevBridge = _execs[i - 1].bridge; require(e.bridgeOutToken != address(0) && e.bridgeOutMin > 0 && e.feeInBridgeOutToken > 0, "invalid exec"); require( e.bridgeOutFallbackToken == address(0) || (e.bridgeOutFallbackMin > 0 && e.feeInBridgeOutFallbackToken > 0), "invalid fallback" ); // bridged tokens and the chain id of the execution are encoded in the sig data so that // no malicious user can temper the fee they have to pay on any execution steps bytes memory execData = abi.encodePacked( prevBridge.toChainId, e.feeInBridgeOutToken, e.bridgeOutToken, e.feeInBridgeOutFallbackToken, e.bridgeOutFallbackToken, // native fee also needs to be agreed upon by chainhop for any subsequent bridge // since the fee is provided by chainhop's executor e.bridge.nativeFee ); data = data.concat(execData); } bytes32 signHash = keccak256(data).toEthSignedMessageHash(); verifySig(signHash, _src.quoteSig); } }
IERC20.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 amount ) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); }
Types.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity 0.8.15; import "./MsgDataTypes.sol"; import "../interfaces/ICodec.sol"; library Types { struct SourceInfo { // A number unique enough to be used in request ID generation. uint64 nonce; // the unix timestamp before which the fee is valid uint64 deadline; // sig of sha3("executor fee", srcChainId, amountIn, tokenIn, deadline, toChainId, feeInBridgeOutToken, bridgeOutToken, feeInBridgeOutFallbackToken, bridgeOutFallbackToken[, toChainId, feeInBridgeOutToken, bridgeOutToken, feeInBridgeOutFallbackToken, bridgeOutFallbackToken]...) // see _verifyQuote() bytes quoteSig; uint256 amountIn; address tokenIn; bool nativeIn; } function emptySourceInfo() internal pure returns (SourceInfo memory) { return SourceInfo(0, 0, "", 0, address(0), false); } struct DestinationInfo { // The receiving party (the user) of the final output token // note that if an organization user's private key is breached, and if their original receiver is a contract // address, the hacker could deploy a malicious contract with the same address on the different chain and hence // get access to the user's pocket funds on that chain. // WARNING users should make sure their own deployer key's safety or that the receiver is // 1. not a reproducable address on any of the chains that chainhop supports // 2. a contract that they already deployed on all the chains that chainhop supports // 3. an EOA address receiver; bool nativeOut; } struct ExecutionInfo { ICodec.SwapDescription swap; BridgeInfo bridge; address bridgeOutToken; // some bridges utilize a intermediary token (e.g. hToken for Hop and anyToken for Multichain) // in cases where there isn't enough underlying token liquidity on the dst chain, the user/pocket // could receive this token as a fallback. remote ExecutionNode needs to know what this token is // in order to check whether a fallback has happened and refund the user. address bridgeOutFallbackToken; // the minimum that remote ExecutionNode needs to receive in order to allow the swap message // to execute. note that this differs from a normal slippages controlling variable and is // purely used to deter DoS attacks (detailed in ExecutionNode). uint256 bridgeOutMin; uint256 bridgeOutFallbackMin; // executor fee uint256 feeInBridgeOutToken; // in case the bridging result in in fallback tokens, this is the amount of the fee that // chainhop charges uint256 feeInBridgeOutFallbackToken; } struct BridgeInfo { uint64 toChainId; // bridge provider identifier string bridgeProvider; // Bridge transfers quoted and abi encoded by chainhop backend server. // Bridge adapter implementations need to decode this themselves. bytes bridgeParams; // the native fee required by the bridge provider uint256 nativeFee; } struct Message { bytes32 id; Types.ExecutionInfo[] execs; Types.DestinationInfo dst; } }
MsgDataTypes.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity 0.8.15; library MsgDataTypes { // bridge operation type at the sender side (src chain) enum BridgeSendType { Null, Liquidity, PegDeposit, PegBurn, PegV2Deposit, PegV2Burn, PegV2BurnFrom } // bridge operation type at the receiver side (dst chain) enum TransferType { Null, LqRelay, // relay through liquidity bridge LqWithdraw, // withdraw from liquidity bridge PegMint, // mint through pegged token bridge PegWithdraw, // withdraw from original token vault PegV2Mint, // mint through pegged token bridge v2 PegV2Withdraw // withdraw from original token vault v2 } enum MsgType { MessageWithTransfer, MessageOnly } enum TxStatus { Null, Success, Fail, Fallback, Pending // transient state within a transaction } struct TransferInfo { TransferType t; address sender; address receiver; address token; uint256 amount; uint64 wdseq; // only needed for LqWithdraw (refund) uint64 srcChainId; bytes32 refId; bytes32 srcTxHash; // src chain msg tx hash } struct RouteInfo { address sender; address receiver; uint64 srcChainId; bytes32 srcTxHash; // src chain msg tx hash } struct MsgWithTransferExecutionParams { bytes message; TransferInfo transfer; bytes[] sigs; address[] signers; uint256[] powers; } struct BridgeTransferParams { bytes request; bytes[] sigs; address[] signers; uint256[] powers; } }
ICodec.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.0; interface ICodec { struct SwapDescription { address dex; // the DEX to use for the swap, zero address implies no swap needed bytes data; // the data to call the dex with } function decodeCalldata(SwapDescription calldata swap) external view returns ( uint256 amountIn, address tokenIn, address tokenOut ); function encodeCalldataWithOverride( bytes calldata data, uint256 amountInOverride, address receiverOverride ) external pure returns (bytes memory swapCalldata); }
MessageReceiver.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity 0.8.15; import "./Ownable.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../interfaces/IMessageReceiver.sol"; abstract contract MessageReceiver is IMessageReceiver, Ownable, Initializable { event MessageBusUpdated(address messageBus); // testMode is used for the ease of testing functions with the "onlyMessageBus" modifier. // WARNING: when testMode is true, ANYONE can call executeMessage functions // this variable can only be set during contract construction and is always not set on mainnets bool public testMode; address public messageBus; constructor(bool _testMode, address _messageBus) { testMode = _testMode; messageBus = _messageBus; } function initMessageReceiver(bool _testMode, address _msgbus) internal onlyInitializing { require(!_testMode || block.chainid == 31337); // only allow testMode on hardhat local network testMode = _testMode; messageBus = _msgbus; emit MessageBusUpdated(messageBus); } function setMessageBus(address _msgbus) public onlyOwner { messageBus = _msgbus; emit MessageBusUpdated(messageBus); } modifier onlyMessageBus() { requireMessageBus(); _; } function requireMessageBus() internal view { if (!testMode) { require(msg.sender == messageBus, "caller is not message bus"); } } /** * @notice Called by MessageBus (MessageBusReceiver) * @param _sender The address of the source app contract * @param _srcChainId The source chain ID where the transfer is originated from * @param _message Arbitrary message bytes originated from and encoded by the source app contract * @param _executor Address who called the MessageBus execution function */ function executeMessage( address _sender, uint64 _srcChainId, bytes calldata _message, address _executor ) external payable virtual returns (ExecutionStatus) {} /** * @notice Called by MessageBus (MessageBusReceiver) to process refund of the original transfer from this contract * @param _token The token address of the original transfer * @param _amount The amount of the original transfer * @param _message The same message associated with the original transfer */ function executeMessageWithTransferRefund( address _token, uint256 _amount, bytes calldata _message ) external payable virtual returns (bool) {} }
Ownable.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.0; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. * * This adds a normal func that setOwner if _owner is address(0). So we can't allow * renounceOwnership. So we can support Proxy based upgradable contract */ abstract contract Ownable { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() { _setOwner(msg.sender); } /** * @dev Only to be called by inherit contracts, in their init func called by Proxy * we require _owner == address(0), which is only possible when it's a delegateCall * because constructor sets _owner in contract state. */ function initOwner() internal { require(_owner == address(0), "owner already set"); _setOwner(msg.sender); } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(owner() == msg.sender, "Ownable: caller is not the owner"); _; } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _setOwner(newOwner); } function _setOwner(address newOwner) private { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } }
IMessageReceiver.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.0; interface IMessageReceiver { enum ExecutionStatus { Fail, // execution failed, finalized Success, // execution succeeded, finalized Retry // execution rejected, can retry later } /** * @notice Called by MessageBus (MessageBusReceiver) * @param _sender The address of the source app contract * @param _srcChainId The source chain ID where the transfer is originated from * @param _message Arbitrary message bytes originated from and encoded by the source app contract * @param _executor Address who called the MessageBus execution function */ function executeMessage( address _sender, uint64 _srcChainId, bytes calldata _message, address _executor ) external payable returns (ExecutionStatus); /** * @notice Called by MessageBus (MessageBusReceiver) to process refund of the original transfer from this contract * @param _token The token address of the original transfer * @param _amount The amount of the original transfer * @param _message The same message associated with the original transfer */ function executeMessageWithTransferRefund( address _token, uint256 _amount, bytes calldata _message ) external payable returns (bool); }
Pauser.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; import "./Pausable.sol"; import "./Ownable.sol"; abstract contract Pauser is Ownable, Pausable { mapping(address => bool) public pausers; event PauserAdded(address account); event PauserRemoved(address account); constructor() { _addPauser(msg.sender); } modifier onlyPauser() { require(isPauser(msg.sender), "Caller is not pauser"); _; } function pause() public onlyPauser { _pause(); } function unpause() public onlyPauser { _unpause(); } function isPauser(address account) public view returns (bool) { return pausers[account]; } function addPauser(address account) public onlyOwner { _addPauser(account); } function removePauser(address account) public onlyOwner { _removePauser(account); } function renouncePauser() public { _removePauser(msg.sender); } function _addPauser(address account) private { require(!isPauser(account), "Account is already pauser"); pausers[account] = true; emit PauserAdded(account); } function _removePauser(address account) private { require(isPauser(account), "Account is not pauser"); pausers[account] = false; emit PauserRemoved(account); } }
Pausable.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol) pragma solidity ^0.8.0; import "@openzeppelin/contracts/utils/Context.sol"; /** * @dev modified version of OZ's Pausable to support Celer IM's message ABORT op code. * @dev Contract module which allows children to implement an emergency stop * mechanism that can be triggered by an authorized account. * * This module is used through inheritance. It will make available the * modifiers `whenNotPaused` and `whenPaused`, which can be applied to * the functions of your contract. Note that they will not be pausable by * simply including this module, only once the modifiers are put in place. */ abstract contract Pausable is Context { /** * @dev Emitted when the pause is triggered by `account`. */ event Paused(address account); /** * @dev Emitted when the pause is lifted by `account`. */ event Unpaused(address account); bool private _paused; /** * @dev Initializes the contract in unpaused state. */ constructor() { _paused = false; } /** * @dev Modifier to make a function callable only when the contract is not paused. * * Requirements: * * - The contract must not be paused. */ modifier whenNotPaused() { _requireNotPaused(); _; } /** * @dev Modifier to make a function callable only when the contract is paused. * * Requirements: * * - The contract must be paused. */ modifier whenPaused() { _requirePaused(); _; } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { return _paused; } /** * @dev Throws if the contract is paused. */ function _requireNotPaused() internal view virtual { require(!paused(), "MSG::ABORT:paused"); } /** * @dev Throws if the contract is not paused. */ function _requirePaused() internal view virtual { require(paused(), "MSG::ABORT:not paused"); } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); } }
NativeWrap.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; import "./Ownable.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; abstract contract NativeWrap is Ownable, Initializable { address public nativeWrap; event NativeWrapUpdated(address nativeWrap); constructor(address _nativeWrap) { nativeWrap = _nativeWrap; } function initNativeWrap(address _nativeWrap) internal onlyInitializing { _setNativeWrap(_nativeWrap); } function setNativeWrap(address _nativeWrap) external onlyOwner { _setNativeWrap(_nativeWrap); } function _setNativeWrap(address _nativeWrap) private { nativeWrap = _nativeWrap; emit NativeWrapUpdated(_nativeWrap); } receive() external payable {} }
Bytes.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; library Bytes { uint256 internal constant WORD_SIZE = 32; function concat(bytes memory self, bytes memory other) internal pure returns (bytes memory) { bytes memory ret = new bytes(self.length + other.length); (uint256 src, uint256 srcLen) = fromBytes(self); (uint256 src2, uint256 src2Len) = fromBytes(other); (uint256 dest, ) = fromBytes(ret); uint256 dest2 = dest + srcLen; copy(src, dest, srcLen); copy(src2, dest2, src2Len); return ret; } function fromBytes(bytes memory bts) internal pure returns (uint256 addr, uint256 len) { len = bts.length; assembly { addr := add(bts, 32) } } function copy( uint256 src, uint256 dest, uint256 len ) internal pure { // Copy word-length chunks while possible for (; len >= WORD_SIZE; len -= WORD_SIZE) { assembly { mstore(dest, mload(src)) } dest += WORD_SIZE; src += WORD_SIZE; } if (len == 0) return; // Copy remaining bytes uint256 mask = 256**(WORD_SIZE - len) - 1; assembly { let srcpart := and(mload(src), not(mask)) let destpart := and(mload(dest), mask) mstore(dest, or(destpart, srcpart)) } } }
IBridgeAdapter.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; interface IBridgeAdapter { function bridge( uint64 _dstChainId, // the address that the fund is transfered to on the destination chain address _receiver, uint256 _amount, address _token, // Bridge transfers quoted and abi encoded by chainhop backend server. // Bridge adapter implementations need to decode this themselves. bytes memory _bridgeParams ) external payable returns (bytes memory bridgeResp); }
IExecutionNodeEvents.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity 0.8.15; import "../lib/Types.sol"; interface IExecutionNodeEvents { /** * @notice Emitted when operations on dst chain is done. * @param id see _computeId() * @param amountOut the amount of tokenOut from this step * @param tokenOut the token that is outputted from this step */ event StepExecuted(bytes32 id, uint256 amountOut, address tokenOut); event PocketFundClaimed(address receiver, uint256 erc20Amount, address token, uint256 nativeAmount); }
IWETH.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IWETH is IERC20 { function deposit() external payable; function withdraw(uint256) external; }
IMessageBus.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.0; import "../lib/MsgDataTypes.sol"; interface IMessageBus { event Executed( MsgDataTypes.MsgType msgType, bytes32 msgId, MsgDataTypes.TxStatus status, address indexed receiver, uint64 srcChainId, bytes32 srcTxHash ); function liquidityBridge() external view returns (address); function pegBridge() external view returns (address); function pegBridgeV2() external view returns (address); function pegVault() external view returns (address); function pegVaultV2() external view returns (address); function feeBase() external view returns (uint256); function feePerByte() external view returns (uint256); /** * @notice Calculates the required fee for the message. * @param _message Arbitrary message bytes to be decoded by the destination app contract. @ @return The required fee. */ function calcFee(bytes calldata _message) external view returns (uint256); /** * @notice Sends a message to an app on another chain via MessageBus without an associated transfer. * A fee is charged in the native gas token. * @param _receiver The address of the destination app contract. * @param _dstChainId The destination chain ID. * @param _message Arbitrary message bytes to be decoded by the destination app contract. */ function sendMessage( address _receiver, uint256 _dstChainId, bytes calldata _message ) external payable; /** * @notice Sends a message associated with a transfer to an app on another chain via MessageBus without an associated transfer. * A fee is charged in the native token. * @param _receiver The address of the destination app contract. * @param _dstChainId The destination chain ID. * @param _srcBridge The bridge contract to send the transfer with. * @param _srcTransferId The transfer ID. * @param _dstChainId The destination chain ID. * @param _message Arbitrary message bytes to be decoded by the destination app contract. */ function sendMessageWithTransfer( address _receiver, uint256 _dstChainId, address _srcBridge, bytes32 _srcTransferId, bytes calldata _message ) external payable; /** * @notice Withdraws message fee in the form of native gas token. * @param _account The address receiving the fee. * @param _cumulativeFee The cumulative fee credited to the account. Tracked by SGN. * @param _sigs The list of signatures sorted by signing addresses in ascending order. A withdrawal must be * signed-off by +2/3 of the sigsVerifier's current signing power to be delivered. * @param _signers The sorted list of signers. * @param _powers The signing powers of the signers. */ function withdrawFee( address _account, uint256 _cumulativeFee, bytes[] calldata _sigs, address[] calldata _signers, uint256[] calldata _powers ) external; /** * @notice Execute a message with a successful transfer. * @param _message Arbitrary message bytes originated from and encoded by the source app contract * @param _transfer The transfer info. * @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by * +2/3 of the sigsVerifier's current signing power to be delivered. * @param _signers The sorted list of signers. * @param _powers The signing powers of the signers. */ function executeMessageWithTransfer( bytes calldata _message, MsgDataTypes.TransferInfo calldata _transfer, bytes[] calldata _sigs, address[] calldata _signers, uint256[] calldata _powers ) external payable; /** * @notice Execute a message with a refunded transfer. * @param _message Arbitrary message bytes originated from and encoded by the source app contract * @param _transfer The transfer info. * @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by * +2/3 of the sigsVerifier's current signing power to be delivered. * @param _signers The sorted list of signers. * @param _powers The signing powers of the signers. */ function executeMessageWithTransferRefund( bytes calldata _message, // the same message associated with the original transfer MsgDataTypes.TransferInfo calldata _transfer, bytes[] calldata _sigs, address[] calldata _signers, uint256[] calldata _powers ) external payable; /** * @notice Execute a message not associated with a transfer. * @param _message Arbitrary message bytes originated from and encoded by the source app contract * @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by * +2/3 of the sigsVerifier's current signing power to be delivered. * @param _signers The sorted list of signers. * @param _powers The signing powers of the signers. */ function executeMessage( bytes calldata _message, MsgDataTypes.RouteInfo calldata _route, bytes[] calldata _sigs, address[] calldata _signers, uint256[] calldata _powers ) external payable; }
MsgDataTypes.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity 0.8.15; library MsgDataTypes { // bridge operation type at the sender side (src chain) enum BridgeSendType { Null, Liquidity, PegDeposit, PegBurn, PegV2Deposit, PegV2Burn, PegV2BurnFrom } // bridge operation type at the receiver side (dst chain) enum TransferType { Null, LqRelay, // relay through liquidity bridge LqWithdraw, // withdraw from liquidity bridge PegMint, // mint through pegged token bridge PegWithdraw, // withdraw from original token vault PegV2Mint, // mint through pegged token bridge v2 PegV2Withdraw // withdraw from original token vault v2 } enum MsgType { MessageWithTransfer, MessageOnly } enum TxStatus { Null, Success, Fail, Fallback, Pending // transient state within a transaction } struct TransferInfo { TransferType t; address sender; address receiver; address token; uint256 amount; uint64 wdseq; // only needed for LqWithdraw (refund) uint64 srcChainId; bytes32 refId; bytes32 srcTxHash; // src chain msg tx hash } struct RouteInfo { address sender; address receiver; uint64 srcChainId; bytes32 srcTxHash; // src chain msg tx hash } struct MsgWithTransferExecutionParams { bytes message; TransferInfo transfer; bytes[] sigs; address[] signers; uint256[] powers; } struct BridgeTransferParams { bytes request; bytes[] sigs; address[] signers; uint256[] powers; } }
BridgeRegistry.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../interfaces/IBridgeAdapter.sol"; import "../lib/Ownable.sol"; /** * @title Manages a list of supported bridges * @author lionelhoho * @author Padoriku */ abstract contract BridgeRegistry is Ownable, Initializable { event SupportedBridgesUpdated(string[] providers, address[] adapters); mapping(bytes32 => IBridgeAdapter) public bridges; function initBridgeRegistry(string[] memory _providers, address[] memory _adapters) internal onlyInitializing { _setSupportedbridges(_providers, _adapters); } // to disable a bridge, set the bridge addr of the corresponding provider to address(0) function setSupportedBridges(string[] memory _providers, address[] memory _adapters) external onlyOwner { _setSupportedbridges(_providers, _adapters); } function _setSupportedbridges(string[] memory _providers, address[] memory _adapters) private { require(_providers.length == _adapters.length, "params size mismatch"); for (uint256 i = 0; i < _providers.length; i++) { bridges[keccak256(bytes(_providers[i]))] = IBridgeAdapter(_adapters[i]); } emit SupportedBridgesUpdated(_providers, _adapters); } }
Ownable.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.0; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. * * This adds a normal func that setOwner if _owner is address(0). So we can't allow * renounceOwnership. So we can support Proxy based upgradable contract */ abstract contract Ownable { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() { _setOwner(msg.sender); } /** * @dev Only to be called by inherit contracts, in their init func called by Proxy * we require _owner == address(0), which is only possible when it's a delegateCall * because constructor sets _owner in contract state. */ function initOwner() internal { require(_owner == address(0), "owner already set"); _setOwner(msg.sender); } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(owner() == msg.sender, "Ownable: caller is not the owner"); _; } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _setOwner(newOwner); } function _setOwner(address newOwner) private { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } }
DexRegistry.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../interfaces/ICodec.sol"; import "../lib/Ownable.sol"; /** * @title Manages a list supported dex * @author Padoriku */ abstract contract DexRegistry is Ownable, Initializable { event DexCodecUpdated(address dex, bytes4 selector, address codec); // supported swap functions // 0x3df02124 exchange(int128,int128,uint256,uint256) // 0xa6417ed6 exchange_underlying(int128,int128,uint256,uint256) // 0x44ee1986 exchange_underlying(int128,int128,uint256,uint256,address) // 0x38ed1739 swapExactTokensForTokens(uint256,uint256,address[],address,uint256) // 0xc04b8d59 exactInput((bytes,address,uint256,uint256,uint256)) // 0xb0431182 clipperSwap(address,address,uint256,uint256) // 0xe449022e uniswapV3Swap(uint256,uint256,uint256[]) // 0x2e95b6c8 unoswap(address,uint256,uint256,bytes32[]) // 0x7c025200 swap(address,(address,address,address,address,uint256,uint256,uint256,bytes),bytes) // 0xd0a3b665 fillOrderRFQ((uint256,address,address,address,address,uint256,uint256),bytes,uint256,uint256) mapping(address => mapping(bytes4 => address)) public dexFunc2Codec; function initDexRegistry( address[] memory _dexList, string[] memory _funcs, address[] memory _codecs ) internal onlyInitializing { _setDexCodecs(_dexList, _funcs, _codecs); } function setDexCodecs( address[] memory _dexList, string[] memory _funcs, address[] memory _codecs ) external onlyOwner { _setDexCodecs(_dexList, _funcs, _codecs); } function _setDexCodecs( address[] memory _dexList, string[] memory _funcs, address[] memory _codecs ) private { require(_dexList.length == _funcs.length && _dexList.length == _codecs.length, "codec lengths mismatch"); for (uint256 i = 0; i < _dexList.length; i++) { bytes4 selector = bytes4(keccak256(bytes(_funcs[i]))); _setDexCodec(_dexList[i], selector, _codecs[i]); } } function _setDexCodec( address _dex, bytes4 _selector, address _codec ) private { address codec = dexFunc2Codec[_dex][_selector]; require(codec != _codec, "nop"); dexFunc2Codec[_dex][_selector] = _codec; emit DexCodecUpdated(_dex, _selector, _codec); } function getCodec(address _dex, bytes4 _selector) internal view returns (ICodec) { require(dexFunc2Codec[_dex][_selector] != address(0), "unsupported dex"); return ICodec(dexFunc2Codec[_dex][_selector]); } }
RemoteExecutionNodeRegistry.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../lib/Ownable.sol"; /** * @title Allows the owner to whitelist remote ExecutionNode addresses * @author Padoriku */ abstract contract RemoteExecutionNodeRegistry is Ownable, Initializable { // chainId => address mapping mapping(uint64 => address) public remotes; event RemotesUpdated(uint64[] chainIds, address[] remotes); function setRemotes(uint64[] memory _chainIds, address[] memory _remotes) external onlyOwner { _setRemotes(_chainIds, _remotes); } function _setRemotes(uint64[] memory _chainIds, address[] memory _remotes) private { require(_chainIds.length == _remotes.length, "remotes length mismatch"); for (uint256 i = 0; i < _chainIds.length; i++) { remotes[_chainIds[i]] = _remotes[i]; } emit RemotesUpdated(_chainIds, _remotes); } modifier onlyRemoteExecutionNode(uint64 _chainId, address _remote) { requireRemoteExecutionNode(_chainId, _remote); _; } function requireRemoteExecutionNode(uint64 _chainId, address _remote) internal view { require(remotes[_chainId] == _remote, "unknown remote"); } }
FeeVaultRegistry.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "../lib/Ownable.sol"; /** * @title Allows the owner to set fee account * @author Padoriku */ abstract contract FeeVaultRegistry is Ownable, Initializable { address public feeVault; event FeeVaultUpdated(address from, address to); function initFeeVaultRegistry(address _vault) internal onlyInitializing { _setFeeVault(_vault); } function setFeeVault(address _vault) external onlyOwner { _setFeeVault(_vault); } function _setFeeVault(address _vault) private { address oldFeeCollector = feeVault; feeVault = _vault; emit FeeVaultUpdated(oldFeeCollector, _vault); } }
SigVerifier.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.15; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "./lib/Ownable.sol"; /** * @title Allows owner to set signer, and verifies signatures * @author Padoriku */ contract SigVerifier is Ownable, Initializable { using ECDSA for bytes32; address public signer; event SignerUpdated(address from, address to); function initSigVerifier(address _signer) internal onlyInitializing { _setSigner(_signer); } function setSigner(address _signer) public onlyOwner { _setSigner(_signer); } function _setSigner(address _signer) private { address oldSigner = signer; signer = _signer; emit SignerUpdated(oldSigner, _signer); } function verifySig(bytes32 _hash, bytes memory _feeSig) internal view { address _signer = _hash.recover(_feeSig); require(_signer == signer, "invalid signer"); } }
Pocket.sol
// SPDX-License-Identifier: GPL-3.0-only pragma solidity 0.8.15; // the pocket is a contract that is to be created conterfactually on the dst chain in the scenario where // there is a dst swap. the main problem the pocket tries to solve is to gain the ability to know when and // by how much the bridged tokens are received. // when chainhop backend builds a cross-chain swap, it calculates a swap id (see _computeSwapId in // ExecutionNode) and the id is used as the salt in generating a pocket address on the dst chain. // this address is then assigned as the receiver of the bridge out tokens on the dst chain to temporarily // hold the funds until the actual pocket contract is deployed at the exact address during the message execution. contract Pocket { function claim(address _token, uint256 _amt) external { address sender = msg.sender; _token.call(abi.encodeWithSelector(0xa9059cbb, sender, _amt)); assembly { // selfdestruct sends all native balance to sender selfdestruct(sender) } } }

Get Cookin'
share iconShare

copy iconNo-Code Deploy
copy iconDownload Source
copy iconnpx cookbookdev i Chainhop-DEX-Aggregator
copy icon

Bytecode

Download

Verification

Download

Recent Use

🍞 0xE856 downloaded

Last Publish

3/12/2023

Solidity Compiler

v0.8.15+commit.e14f2714

Version

1.0.0

Creator

链求君

Cookbook is free.
Any contract you deploy is yours.
Your contract is owned and controlled by you.