// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.12;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interfaces/ICodec.sol";
/**
* @title A codec registry that maps swap function selectors to corresponding codec addresses
* @author Padoriku
*/
abstract contract CodecRegistry is Ownable {
// Initially 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))
mapping(bytes4 => ICodec) public selector2codec;
// not used programmatically, but added for contract transparency
address[] public codecs;
event CodecUpdated(bytes4 selector, address codec);
constructor(string[] memory _funcSigs, address[] memory _codecs) {
require(_funcSigs.length == _codecs.length, "len mm");
for (uint256 i = 0; i < _funcSigs.length; i++) {
bytes4 selector = bytes4(keccak256(bytes(_funcSigs[i])));
_setCodec(selector, _codecs[i]);
}
}
function setCodec(string calldata _funcSig, address _codec) public onlyOwner {
bytes4 selector = bytes4(keccak256(bytes(_funcSig)));
_setCodec(selector, _codec);
emit CodecUpdated(selector, _codec);
}
function _setCodec(bytes4 _selector, address _codec) private {
selector2codec[_selector] = ICodec(_codec);
codecs.push(_codec);
}
function loadCodecs(ICodec.SwapDescription[] memory _swaps) internal view returns (ICodec[] memory) {
ICodec[] memory _codecs = new ICodec[](_swaps.length);
for (uint256 i = 0; i < _swaps.length; i++) {
bytes4 selector = bytes4(_swaps[i].data);
_codecs[i] = selector2codec[selector];
require(address(_codecs[i]) != address(0), "cdc no found");
}
return (_codecs);
}
function getCodec(
bytes4[] memory _selectors,
ICodec[] memory _codecs,
bytes4 _selector
) internal pure returns (ICodec) {
for (uint256 i = 0; i < _codecs.length; i++) {
if (_selector == _selectors[i]) {
return _codecs[i];
}
}
revert("cdc no found");
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.12;
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title Manages a list supported dex
* @author Padoriku
*/
abstract contract DexRegistry is Ownable {
event SupportedDexUpdated(address dex, bytes4 selector, bool enabled);
mapping(address => mapping(bytes4 => bool)) public dexRegistry;
constructor(address[] memory _supportedDexList, string[] memory _supportedFuncs) {
for (uint256 i = 0; i < _supportedDexList.length; i++) {
bytes4 selector = bytes4(keccak256(bytes(_supportedFuncs[i])));
_setSupportedDex(_supportedDexList[i], selector, true);
}
}
function setSupportedDex(
address _dex,
bytes4 _selector,
bool _enabled
) external onlyOwner {
_setSupportedDex(_dex, _selector, _enabled);
emit SupportedDexUpdated(_dex, _selector, _enabled);
}
function _setSupportedDex(
address _dex,
bytes4 _selector,
bool _enabled
) private {
bool enabled = dexRegistry[_dex][_selector];
require(enabled != _enabled, "nop");
dexRegistry[_dex][_selector] = _enabled;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
} else if (error == RecoverError.InvalidSignatureV) {
revert("ECDSA: invalid signature 'v' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
// Check the signature length
// - case 65: r,s,v signature (standard)
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else if (signature.length == 64) {
bytes32 r;
bytes32 vs;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly {
r := mload(add(signature, 0x20))
vs := mload(add(signature, 0x40))
}
return tryRecover(hash, r, vs);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
if (v != 27 && v != 28) {
return (address(0), RecoverError.InvalidSignatureV);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.12;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title Allows the owner to set fee collector and allows fee collectors to collect fees
* @author Padoriku
*/
abstract contract FeeOperator is Ownable {
using SafeERC20 for IERC20;
address public feeCollector;
event FeeCollectorUpdated(address from, address to);
modifier onlyFeeCollector() {
require(msg.sender == feeCollector, "not fee collector");
_;
}
constructor(address _feeCollector) {
feeCollector = _feeCollector;
}
function collectFee(address[] calldata _tokens, address _to) external onlyFeeCollector {
for (uint256 i = 0; i < _tokens.length; i++) {
uint256 balance = IERC20(_tokens[i]).balanceOf(address(this));
IERC20(_tokens[i]).safeTransfer(_to, balance);
}
}
function setFeeCollector(address _feeCollector) external onlyOwner {
address oldFeeCollector = feeCollector;
feeCollector = _feeCollector;
emit FeeCollectorUpdated(oldFeeCollector, _feeCollector);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IBridge {
function send(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage
) external;
function relay(
bytes calldata _relayRequest,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external;
function transfers(bytes32 transferId) external view returns (bool);
function withdraws(bytes32 withdrawId) external view returns (bool);
function withdraw(
bytes calldata _wdmsg,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external;
/**
* @notice Verifies that a message is signed by a quorum among the signers.
* @param _msg signed message
* @param _sigs list of signatures sorted by signer addresses in ascending order
* @param _signers sorted list of current signers
* @param _powers powers of current signers
*/
function verifySigs(
bytes memory _msg,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external view;
}
// 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);
}
// 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);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
import "../lib/MsgDataTypes.sol";
interface IMessageBus {
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);
/**
* @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;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IMessageReceiverApp {
enum ExecutionStatus {
Fail, // execution failed, finalized
Success, // execution succeeded, finalized
Retry // execution rejected, can retry later
}
/**
* @notice Called by MessageBus (MessageBusReceiver) if the process is originated from MessageBus (MessageBusSender)'s
* sendMessageWithTransfer it is only called when the tokens are checked to be arrived at this contract's address.
* @param _sender The address of the source app contract
* @param _token The address of the token that comes out of the bridge
* @param _amount The amount of tokens received at this contract through the cross-chain bridge.
* the contract that implements this contract can safely assume that the tokens will arrive before this
* function is called.
* @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 executeMessageWithTransfer(
address _sender,
address _token,
uint256 _amount,
uint64 _srcChainId,
bytes calldata _message,
address _executor
) external payable returns (ExecutionStatus);
/**
* @notice Only called by MessageBus (MessageBusReceiver) if
* 1. executeMessageWithTransfer reverts, or
* 2. executeMessageWithTransfer returns ExecutionStatus.Fail
* @param _sender The address of the source app contract
* @param _token The address of the token that comes out of the bridge
* @param _amount The amount of tokens received at this contract through the cross-chain bridge.
* the contract that implements this contract can safely assume that the tokens will arrive before this
* function is called.
* @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 executeMessageWithTransferFallback(
address _sender,
address _token,
uint256 _amount,
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
* @param _executor Address who called the MessageBus execution function
*/
function executeMessageWithTransferRefund(
address _token,
uint256 _amount,
bytes calldata _message,
address _executor
) external payable returns (ExecutionStatus);
/**
* @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);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IOriginalTokenVault {
/**
* @notice Lock original tokens to trigger mint at a remote chain's PeggedTokenBridge
* @param _token local token address
* @param _amount locked token amount
* @param _mintChainId destination chainId to mint tokens
* @param _mintAccount destination account to receive minted tokens
* @param _nonce user input to guarantee unique depositId
*/
function deposit(
address _token,
uint256 _amount,
uint64 _mintChainId,
address _mintAccount,
uint64 _nonce
) external;
function records(bytes32 recordId) external view returns (bool);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IOriginalTokenVaultV2 {
/**
* @notice Lock original tokens to trigger mint at a remote chain's PeggedTokenBridge
* @param _token local token address
* @param _amount locked token amount
* @param _mintChainId destination chainId to mint tokens
* @param _mintAccount destination account to receive minted tokens
* @param _nonce user input to guarantee unique depositId
*/
function deposit(
address _token,
uint256 _amount,
uint64 _mintChainId,
address _mintAccount,
uint64 _nonce
) external returns (bytes32);
function records(bytes32 recordId) external view returns (bool);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IPeggedTokenBridge {
/**
* @notice Burn tokens to trigger withdrawal at a remote chain's OriginalTokenVault
* @param _token local token address
* @param _amount locked token amount
* @param _withdrawAccount account who withdraw original tokens on the remote chain
* @param _nonce user input to guarantee unique depositId
*/
function burn(
address _token,
uint256 _amount,
address _withdrawAccount,
uint64 _nonce
) external;
function records(bytes32 recordId) external view returns (bool);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IPeggedTokenBridgeV2 {
/**
* @notice Burn pegged tokens to trigger a cross-chain withdrawal of the original tokens at a remote chain's
* OriginalTokenVault, or mint at another remote chain
* @param _token The pegged token address.
* @param _amount The amount to burn.
* @param _toChainId If zero, withdraw from original vault; otherwise, the remote chain to mint tokens.
* @param _toAccount The account to receive tokens on the remote chain
* @param _nonce A number to guarantee unique depositId. Can be timestamp in practice.
*/
function burn(
address _token,
uint256 _amount,
uint64 _toChainId,
address _toAccount,
uint64 _nonce
) external returns (bytes32);
// same with `burn` above, use openzeppelin ERC20Burnable interface
function burnFrom(
address _token,
uint256 _amount,
uint64 _toChainId,
address _toAccount,
uint64 _nonce
) external returns (bytes32);
/**
* @notice Mint tokens triggered by deposit at a remote chain's OriginalTokenVault.
* @param _request The serialized Mint protobuf.
* @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 mint(
bytes calldata _request,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external returns (bytes32);
function records(bytes32 recordId) external view returns (bool);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.12;
interface IWETH {
function deposit() external payable;
function withdraw(uint256) external;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.12;
import "@openzeppelin/contracts/access/Ownable.sol";
abstract contract MessageBusAddress is Ownable {
event MessageBusUpdated(address messageBus);
address public messageBus;
function setMessageBus(address _messageBus) public onlyOwner {
messageBus = _messageBus;
emit MessageBusUpdated(messageBus);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.12;
import "../interfaces/IMessageReceiverApp.sol";
import "./MessageBusAddress.sol";
abstract contract MessageReceiverApp is IMessageReceiverApp, MessageBusAddress {
modifier onlyMessageBus() {
require(msg.sender == messageBus, "caller is not message bus");
_;
}
/**
* @notice Called by MessageBus (MessageBusReceiver) if the process is originated from MessageBus (MessageBusSender)'s
* sendMessageWithTransfer it is only called when the tokens are checked to be arrived at this contract's address.
* @param _sender The address of the source app contract
* @param _token The address of the token that comes out of the bridge
* @param _amount The amount of tokens received at this contract through the cross-chain bridge.
* the contract that implements this contract can safely assume that the tokens will arrive before this
* function is called.
* @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 executeMessageWithTransfer(
address _sender,
address _token,
uint256 _amount,
uint64 _srcChainId,
bytes calldata _message,
address _executor
) external payable virtual override onlyMessageBus returns (ExecutionStatus) {}
/**
* @notice Only called by MessageBus (MessageBusReceiver) if
* 1. executeMessageWithTransfer reverts, or
* 2. executeMessageWithTransfer returns ExecutionStatus.Fail
* @param _sender The address of the source app contract
* @param _token The address of the token that comes out of the bridge
* @param _amount The amount of tokens received at this contract through the cross-chain bridge.
* the contract that implements this contract can safely assume that the tokens will arrive before this
* function is called.
* @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 executeMessageWithTransferFallback(
address _sender,
address _token,
uint256 _amount,
uint64 _srcChainId,
bytes calldata _message,
address _executor
) external payable virtual override onlyMessageBus 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
* @param _executor Address who called the MessageBus execution function
*/
function executeMessageWithTransferRefund(
address _token,
uint256 _amount,
bytes calldata _message,
address _executor
) external payable virtual override onlyMessageBus returns (ExecutionStatus) {}
/**
* @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 override onlyMessageBus returns (ExecutionStatus) {}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.12;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/IBridge.sol";
import "../interfaces/IOriginalTokenVault.sol";
import "../interfaces/IOriginalTokenVaultV2.sol";
import "../interfaces/IPeggedTokenBridge.sol";
import "../interfaces/IPeggedTokenBridgeV2.sol";
import "../interfaces/IMessageBus.sol";
import "./MsgDataTypes.sol";
library MessageSenderLib {
using SafeERC20 for IERC20;
// ============== Internal library functions called by apps ==============
/**
* @notice Sends a message to an app on another chain via MessageBus without an associated transfer.
* @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.
* @param _messageBus The address of the MessageBus on this chain.
* @param _fee The fee amount to pay to MessageBus.
*/
function sendMessage(
address _receiver,
uint64 _dstChainId,
bytes memory _message,
address _messageBus,
uint256 _fee
) internal {
IMessageBus(_messageBus).sendMessage{value: _fee}(_receiver, _dstChainId, _message);
}
/**
* @notice Sends a message to an app on another chain via MessageBus with an associated transfer.
* @param _receiver The address of the destination app contract.
* @param _token The address of the token to be sent.
* @param _amount The amount of tokens to be sent.
* @param _dstChainId The destination chain ID.
* @param _nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice.
* @param _maxSlippage The max slippage accepted, given as percentage in point (pip). Eg. 5000 means 0.5%.
* Must be greater than minimalMaxSlippage. Receiver is guaranteed to receive at least (100% - max slippage percentage) * amount or the
* transfer can be refunded. Only applicable to the {MsgDataTypes.BridgeSendType.Liquidity}.
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
* @param _bridgeSendType One of the {MsgDataTypes.BridgeSendType} enum.
* @param _messageBus The address of the MessageBus on this chain.
* @param _fee The fee amount to pay to MessageBus.
* @return The transfer ID.
*/
function sendMessageWithTransfer(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage,
bytes memory _message,
MsgDataTypes.BridgeSendType _bridgeSendType,
address _messageBus,
uint256 _fee
) internal returns (bytes32) {
if (_bridgeSendType == MsgDataTypes.BridgeSendType.Liquidity) {
return
sendMessageWithLiquidityBridgeTransfer(
_receiver,
_token,
_amount,
_dstChainId,
_nonce,
_maxSlippage,
_message,
_messageBus,
_fee
);
} else if (
_bridgeSendType == MsgDataTypes.BridgeSendType.PegDeposit ||
_bridgeSendType == MsgDataTypes.BridgeSendType.PegV2Deposit
) {
return
sendMessageWithPegVaultDeposit(
_bridgeSendType,
_receiver,
_token,
_amount,
_dstChainId,
_nonce,
_message,
_messageBus,
_fee
);
} else if (
_bridgeSendType == MsgDataTypes.BridgeSendType.PegBurn ||
_bridgeSendType == MsgDataTypes.BridgeSendType.PegV2Burn
) {
return
sendMessageWithPegBridgeBurn(
_bridgeSendType,
_receiver,
_token,
_amount,
_dstChainId,
_nonce,
_message,
_messageBus,
_fee
);
} else {
revert("bridge type not supported");
}
}
/**
* @notice Sends a message to an app on another chain via MessageBus with an associated liquidity bridge transfer.
* @param _receiver The address of the destination app contract.
* @param _token The address of the token to be sent.
* @param _amount The amount of tokens to be sent.
* @param _dstChainId The destination chain ID.
* @param _nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice.
* @param _maxSlippage The max slippage accepted, given as percentage in point (pip). Eg. 5000 means 0.5%.
* Must be greater than minimalMaxSlippage. Receiver is guaranteed to receive at least (100% - max slippage percentage) * amount or the
* transfer can be refunded.
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
* @param _messageBus The address of the MessageBus on this chain.
* @param _fee The fee amount to pay to MessageBus.
* @return The transfer ID.
*/
function sendMessageWithLiquidityBridgeTransfer(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage,
bytes memory _message,
address _messageBus,
uint256 _fee
) internal returns (bytes32) {
address bridge = IMessageBus(_messageBus).liquidityBridge();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
IBridge(bridge).send(_receiver, _token, _amount, _dstChainId, _nonce, _maxSlippage);
bytes32 transferId = keccak256(
abi.encodePacked(address(this), _receiver, _token, _amount, _dstChainId, _nonce, uint64(block.chainid))
);
IMessageBus(_messageBus).sendMessageWithTransfer{value: _fee}(
_receiver,
_dstChainId,
bridge,
transferId,
_message
);
return transferId;
}
/**
* @notice Sends a message to an app on another chain via MessageBus with an associated OriginalTokenVault deposit.
* @param _receiver The address of the destination app contract.
* @param _token The address of the token to be sent.
* @param _amount The amount of tokens to be sent.
* @param _dstChainId The destination chain ID.
* @param _nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice.
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
* @param _messageBus The address of the MessageBus on this chain.
* @param _fee The fee amount to pay to MessageBus.
* @return The transfer ID.
*/
function sendMessageWithPegVaultDeposit(
MsgDataTypes.BridgeSendType _bridgeSendType,
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
bytes memory _message,
address _messageBus,
uint256 _fee
) internal returns (bytes32) {
address pegVault;
if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegDeposit) {
pegVault = IMessageBus(_messageBus).pegVault();
} else {
pegVault = IMessageBus(_messageBus).pegVaultV2();
}
IERC20(_token).safeIncreaseAllowance(pegVault, _amount);
bytes32 transferId;
if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegDeposit) {
IOriginalTokenVault(pegVault).deposit(_token, _amount, _dstChainId, _receiver, _nonce);
transferId = keccak256(
abi.encodePacked(address(this), _token, _amount, _dstChainId, _receiver, _nonce, uint64(block.chainid))
);
} else {
transferId = IOriginalTokenVaultV2(pegVault).deposit(_token, _amount, _dstChainId, _receiver, _nonce);
}
IMessageBus(_messageBus).sendMessageWithTransfer{value: _fee}(
_receiver,
_dstChainId,
pegVault,
transferId,
_message
);
return transferId;
}
/**
* @notice Sends a message to an app on another chain via MessageBus with an associated PeggedTokenBridge burn.
* @param _receiver The address of the destination app contract.
* @param _token The address of the token to be sent.
* @param _amount The amount of tokens to be sent.
* @param _dstChainId The destination chain ID.
* @param _nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice.
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
* @param _messageBus The address of the MessageBus on this chain.
* @param _fee The fee amount to pay to MessageBus.
* @return The transfer ID.
*/
function sendMessageWithPegBridgeBurn(
MsgDataTypes.BridgeSendType _bridgeSendType,
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
bytes memory _message,
address _messageBus,
uint256 _fee
) internal returns (bytes32) {
address pegBridge;
if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegBurn) {
pegBridge = IMessageBus(_messageBus).pegBridge();
} else {
pegBridge = IMessageBus(_messageBus).pegBridgeV2();
}
IERC20(_token).safeIncreaseAllowance(pegBridge, _amount);
bytes32 transferId;
if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegBurn) {
IPeggedTokenBridge(pegBridge).burn(_token, _amount, _receiver, _nonce);
transferId = keccak256(
abi.encodePacked(address(this), _token, _amount, _receiver, _nonce, uint64(block.chainid))
);
} else {
transferId = IPeggedTokenBridgeV2(pegBridge).burn(_token, _amount, _dstChainId, _receiver, _nonce);
}
// handle cases where certain tokens do not spend allowance for role-based burn
IERC20(_token).safeApprove(pegBridge, 0);
IMessageBus(_messageBus).sendMessageWithTransfer{value: _fee}(
_receiver,
_dstChainId,
pegBridge,
transferId,
_message
);
return transferId;
}
/**
* @notice Sends a token transfer via a bridge.
* @param _receiver The address of the destination app contract.
* @param _token The address of the token to be sent.
* @param _amount The amount of tokens to be sent.
* @param _dstChainId The destination chain ID.
* @param _nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice.
* @param _maxSlippage The max slippage accepted, given as percentage in point (pip). Eg. 5000 means 0.5%.
* Must be greater than minimalMaxSlippage. Receiver is guaranteed to receive at least (100% - max slippage percentage) * amount or the
* transfer can be refunded.
* @param _bridgeSendType One of the {MsgDataTypes.BridgeSendType} enum.
*/
function sendTokenTransfer(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage,
MsgDataTypes.BridgeSendType _bridgeSendType,
address _bridge
) internal {
IERC20(_token).safeIncreaseAllowance(_bridge, _amount);
if (_bridgeSendType == MsgDataTypes.BridgeSendType.Liquidity) {
IBridge(_bridge).send(_receiver, _token, _amount, _dstChainId, _nonce, _maxSlippage);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegDeposit) {
IOriginalTokenVault(_bridge).deposit(_token, _amount, _dstChainId, _receiver, _nonce);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegBurn) {
IPeggedTokenBridge(_bridge).burn(_token, _amount, _receiver, _nonce);
// handle cases where certain tokens do not spend allowance for role-based burn
IERC20(_token).safeApprove(_bridge, 0);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegV2Deposit) {
IOriginalTokenVaultV2(_bridge).deposit(_token, _amount, _dstChainId, _receiver, _nonce);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegV2Burn) {
IPeggedTokenBridgeV2(_bridge).burn(_token, _amount, _dstChainId, _receiver, _nonce);
// handle cases where certain tokens do not spend allowance for role-based burn
IERC20(_token).safeApprove(_bridge, 0);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegV2BurnFrom) {
IPeggedTokenBridgeV2(_bridge).burnFrom(_token, _amount, _dstChainId, _receiver, _nonce);
// handle cases where certain tokens do not spend allowance for role-based burn
IERC20(_token).safeApprove(_bridge, 0);
} else {
revert("bridge type not supported");
}
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.12;
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;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @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.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @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() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @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");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.12;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title Allows owner to set signer, and verifies signatures
* @author Padoriku
*/
contract SigVerifier is Ownable {
using ECDSA for bytes32;
address public signer;
event SignerUpdated(address from, address to);
constructor(address _signer) {
signer = _signer;
}
function setSigner(address _signer) public onlyOwner {
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");
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.12;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./CodecRegistry.sol";
import "./interfaces/ICodec.sol";
import "./interfaces/IWETH.sol";
import "./DexRegistry.sol";
/**
* @title Loads codecs for the swaps and performs swap actions
* @author Padoriku
*/
contract Swapper is CodecRegistry, DexRegistry {
using SafeERC20 for IERC20;
constructor(
string[] memory _funcSigs,
address[] memory _codecs,
address[] memory _supportedDexList,
string[] memory _supportedDexFuncs
) DexRegistry(_supportedDexList, _supportedDexFuncs) CodecRegistry(_funcSigs, _codecs) {}
/**
* @dev Checks the input swaps for that tokenIn and tokenOut for every swap should be the same
* @param _swaps the swaps the check
* @return sumAmtIn the sum of all amountIns in the swaps
* @return tokenIn the input token of the swaps
* @return tokenOut the desired output token of the swaps
* @return codecs a list of codecs which each of them corresponds to a swap
*/
function sanitizeSwaps(ICodec.SwapDescription[] memory _swaps)
internal
view
returns (
uint256 sumAmtIn,
address tokenIn,
address tokenOut,
ICodec[] memory codecs // _codecs[i] is for _swaps[i]
)
{
address prevTokenIn;
address prevTokenOut;
codecs = loadCodecs(_swaps);
for (uint256 i = 0; i < _swaps.length; i++) {
require(dexRegistry[_swaps[i].dex][bytes4(_swaps[i].data)], "unsupported dex");
(uint256 _amountIn, address _tokenIn, address _tokenOut) = codecs[i].decodeCalldata(_swaps[i]);
require(prevTokenIn == address(0) || prevTokenIn == _tokenIn, "tkin mismatch");
prevTokenIn = _tokenIn;
require(prevTokenOut == address(0) || prevTokenOut == _tokenOut, "tko mismatch");
prevTokenOut = _tokenOut;
sumAmtIn += _amountIn;
tokenIn = _tokenIn;
tokenOut = _tokenOut;
}
}
/**
* @notice Executes the swaps, decode their return values and sums the returned amount
* @dev This function is intended to be used on src chain only
* @dev This function immediately fails (return false) if any swaps fail. There is no "partial fill" on src chain
* @param _swaps swaps. this function assumes that the swaps are already sanitized
* @param _codecs the codecs for each swap
* @return ok whether the operation is successful
* @return sumAmtOut the sum of all amounts gained from swapping
*/
function executeSwaps(
ICodec.SwapDescription[] memory _swaps,
ICodec[] memory _codecs // _codecs[i] is for _swaps[i]
) internal returns (bool ok, uint256 sumAmtOut) {
for (uint256 i = 0; i < _swaps.length; i++) {
(uint256 amountIn, address tokenIn, address tokenOut) = _codecs[i].decodeCalldata(_swaps[i]);
bytes memory data = _codecs[i].encodeCalldataWithOverride(_swaps[i].data, amountIn, address(this));
IERC20(tokenIn).safeIncreaseAllowance(_swaps[i].dex, amountIn);
uint256 balBefore = IERC20(tokenOut).balanceOf(address(this));
(ok, ) = _swaps[i].dex.call(data);
if (!ok) {
return (false, 0);
}
uint256 balAfter = IERC20(tokenOut).balanceOf(address(this));
sumAmtOut += balAfter - balBefore;
}
}
/**
* @notice Executes the swaps with override, redistributes amountIns for each swap route,
* decode their return values and sums the returned amount
* @dev This function is intended to be used on dst chain only
* @param _swaps swaps to execute. this function assumes that the swaps are already sanitized
* @param _codecs the codecs for each swap
* @param _amountInOverride the amountIn to substitute the amountIns in swaps for
* @dev _amountInOverride serves the purpose of correcting the estimated amountIns to actual bridge outs
* @dev _amountInOverride is also distributed according to the weight of each original amountIn
* @return sumAmtOut the sum of all amounts gained from swapping
* @return sumAmtFailed the sum of all amounts that fails to swap
*/
function executeSwapsWithOverride(
ICodec.SwapDescription[] memory _swaps,
ICodec[] memory _codecs, // _codecs[i] is for _swaps[i]
uint256 _amountInOverride,
bool _allowPartialFill
) internal returns (uint256 sumAmtOut, uint256 sumAmtFailed) {
(uint256[] memory amountIns, address tokenIn, address tokenOut) = _redistributeAmountIn(
_swaps,
_amountInOverride,
_codecs
);
uint256 balBefore = IERC20(tokenOut).balanceOf(address(this));
// execute the swaps with adjusted amountIns
for (uint256 i = 0; i < _swaps.length; i++) {
bytes memory swapCalldata = _codecs[i].encodeCalldataWithOverride(
_swaps[i].data,
amountIns[i],
address(this)
);
IERC20(tokenIn).safeIncreaseAllowance(_swaps[i].dex, amountIns[i]);
(bool ok, ) = _swaps[i].dex.call(swapCalldata);
require(ok || _allowPartialFill, "swap failed");
if (!ok) {
sumAmtFailed += amountIns[i];
}
}
uint256 balAfter = IERC20(tokenOut).balanceOf(address(this));
sumAmtOut = balAfter - balBefore;
require(sumAmtOut > 0, "all swaps failed");
}
/// @notice distributes the _amountInOverride to the swaps base on how much each original amountIns weight
function _redistributeAmountIn(
ICodec.SwapDescription[] memory _swaps,
uint256 _amountInOverride,
ICodec[] memory _codecs
)
private
view
returns (
uint256[] memory amountIns,
address tokenIn,
address tokenOut
)
{
uint256 sumAmtIn;
amountIns = new uint256[](_swaps.length);
// compute sumAmtIn and collect amountIns
for (uint256 i = 0; i < _swaps.length; i++) {
uint256 amountIn;
(amountIn, tokenIn, tokenOut) = _codecs[i].decodeCalldata(_swaps[i]);
sumAmtIn += amountIn;
amountIns[i] = amountIn;
}
// compute adjusted amountIns with regard to the weight of each amountIns in total amountIn
for (uint256 i = 0; i < amountIns.length; i++) {
amountIns[i] = (_amountInOverride * amountIns[i]) / sumAmtIn;
}
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.12;
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 "./lib/MessageSenderLib.sol";
import "./lib/MessageReceiverApp.sol";
import "./lib/MsgDataTypes.sol";
import "./FeeOperator.sol";
import "./SigVerifier.sol";
import "./Swapper.sol";
import "./interfaces/ICodec.sol";
/**
* @author Chainhop Dex Team
* @author Padoriku
* @title An app that enables swapping on a chain, transferring to another chain and swapping
* another time on the destination chain before sending the result tokens to a user
*/
contract TransferSwapper is MessageReceiverApp, Swapper, SigVerifier, FeeOperator, ReentrancyGuard {
using SafeERC20 for IERC20;
using ECDSA for bytes32;
struct TransferDescription {
address receiver; // the receiving party (the user) of the final output token
uint64 dstChainId; // destination chain id
uint32 maxBridgeSlippage; // user defined maximum allowed slippage (pip) at bridge
MsgDataTypes.BridgeSendType bridgeType; // type of the bridge to use
uint64 nonce; // nonce is needed for de-dup tx at this contract and bridge
bool nativeIn; // whether to check msg.value and wrap token before swapping/sending
bool nativeOut; // whether to unwrap before sending the final token to user
uint256 fee; // this fee is only executor fee. it does not include msg bridge fee
uint256 feeDeadline; // the unix timestamp before which the fee is valid
// sig of sha3("executor fee", srcChainId, dstChainId, amountIn, tokenIn, feeDeadline, fee)
// see _verifyFee()
bytes feeSig;
// IMPORTANT: amountIn and tokenIn are completely ignored if src chain has a swap
// these two fields are only meant for the scenario where no swaps are needed on src chain
uint256 amountIn;
address tokenIn;
address dstTokenOut; // the final output token, emitted in event for display purpose only
// in case of multi route swaps, whether to allow the successful swaps to go through
// and sending the amountIn of the failed swaps back to user
bool allowPartialFill;
}
struct Request {
bytes32 id; // see _computeId()
ICodec.SwapDescription[] swaps; // the swaps need to happen on the destination chain
address receiver; // see TransferDescription.receiver
bool nativeOut; // see TransferDescription.nativeOut
uint256 fee; // see TransferDescription.fee
bool allowPartialFill; // see TransferDescription.allowPartialFill
}
/**
* @notice Denotes the status of a cross-chain transfer/swap request
* @dev Partially filled requests are considered 'Succeeded'. There is no 'Failed' state as
* it's only possible if everything reverts and there is no successful transaction
* @param Null An empty status that should never be reached
* @param Succeeded Transfer/swap has succeeded and funds are received by the receiver
* @param Fallback Swaps have failed on the dst chain, and bridge tokens are refunded to receiver
*/
enum RequestStatus {
Null,
Succeeded,
Fallback
}
event NativeWrapUpdated(address nativeWrap);
/**
* @notice Emitted when requested dstChainId == srcChainId, no bridging
* @param id see _computeId()
* @param amountIn the input amount approved by the sender
* @param tokenIn the input token approved by the sender
* @param amountOut the output amount gained after swapping using the input tokens
* @param tokenOut the output token gained after swapping using the input tokens
*/
event DirectSwap(bytes32 id, uint256 amountIn, address tokenIn, uint256 amountOut, address tokenOut);
/**
* @notice Emitted when operations on src chain is done, the transfer is sent through the bridge
* @param id see _computeId()
* @param transferId the src transfer id produced by MessageSenderLib.sendMessageWithTransfer()
* @param dstChainId destination chain id
* @param srcAmount input amount approved by the sender
* @param srcToken the input token approved by the sender
* @param dstToken the final output token (after bridging and swapping) desired by the sender
*/
event RequestSent(
bytes32 id,
bytes32 transferId,
uint64 dstChainId,
uint256 srcAmount,
address srcToken,
address dstToken
);
// emitted when operations on dst chain is done.
// dstAmount is denominated by dstToken, refundAmount is denominated by bridge out token.
// if refundAmount is a non-zero number, it means the "allow partial fill" option is turned on.
/**
* @notice Emitted when operations on dst chain is done.
* @param id see _computeId()
* @param dstAmount the final output token (after bridging and swapping) desired by the sender
* @param refundAmount the amount refunded to the receiver in bridge token
* @dev refundAmount may be fill by either a complete refund or when allowPartialFill is on and
* some swaps fails in the swap routes
* @param refundToken bridge out token
* @param feeCollected the fee chainhop deducts from bridge out token
* @param status see RequestStatus
*/
event RequestDone(
bytes32 id,
uint256 dstAmount,
uint256 refundAmount,
address refundToken,
uint256 feeCollected,
RequestStatus status
);
/// @notice erc20 wrap of the gas token of this chain, e.g. WETH
address public nativeWrap;
constructor(
address _messageBus,
address _nativeWrap,
address _signer,
address _feeCollector,
string[] memory _funcSigs,
address[] memory _codecs,
address[] memory _supportedDexList,
string[] memory _supportedDexFuncs
)
Swapper(_funcSigs, _codecs, _supportedDexList, _supportedDexFuncs)
FeeOperator(_feeCollector)
SigVerifier(_signer)
{
messageBus = _messageBus;
nativeWrap = _nativeWrap;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Source chain functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* @notice swaps if needed, then transfer the token to another chain along with an instruction on how to swap
* on that chain
* @param _dstTransferSwapper the address of the TransferSwapper on the destination chain
*/
function transferWithSwap(
address _dstTransferSwapper,
TransferDescription calldata _desc,
ICodec.SwapDescription[] calldata _srcSwaps,
ICodec.SwapDescription[] calldata _dstSwaps
) external payable nonReentrant {
// a request needs to incur a swap, a transfer, or both. otherwise it's a nop and we revert early to save gas
require(_srcSwaps.length != 0 || _desc.dstChainId != uint64(block.chainid), "nop");
require(_srcSwaps.length != 0 || (_desc.amountIn != 0 && _desc.tokenIn != address(0)), "nop");
uint256 amountIn = _desc.amountIn;
address tokenIn = _desc.tokenIn;
address tokenOut = _desc.tokenIn;
ICodec[] memory codecs;
if (_srcSwaps.length != 0) {
(amountIn, tokenIn, tokenOut, codecs) = sanitizeSwaps(_srcSwaps);
require(tokenIn == _desc.tokenIn, "tkin mm");
}
if (_desc.nativeIn) {
require(tokenIn == nativeWrap, "tkin no nativeWrap");
require(msg.value >= amountIn, "insfcnt amt"); // insufficient amount
IWETH(nativeWrap).deposit{value: amountIn}();
} else {
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
}
_swapAndSend(_dstTransferSwapper, amountIn, tokenIn, tokenOut, _srcSwaps, _dstSwaps, _desc, codecs);
}
function _swapAndSend(
address _dstTransferSwapper,
uint256 _amountIn,
address _tokenIn,
address _tokenOut,
ICodec.SwapDescription[] memory _srcSwaps,
ICodec.SwapDescription[] memory _dstSwaps,
TransferDescription memory _desc,
ICodec[] memory _codecs
) private {
// swap if needed
uint256 amountOut = _amountIn;
if (_srcSwaps.length != 0) {
bool ok;
(ok, amountOut) = executeSwaps(_srcSwaps, _codecs);
require(ok, "swap fail");
}
bytes32 id = _computeId(_desc.receiver, _desc.nonce);
// direct send if needed
if (_desc.dstChainId == uint64(block.chainid)) {
emit DirectSwap(id, _amountIn, _tokenIn, amountOut, _tokenOut);
_sendToken(_tokenOut, amountOut, _desc.receiver, _desc.nativeOut);
return;
}
_verifyFee(_desc, _amountIn, _tokenIn);
uint256 msgFee = msg.value;
if (_desc.nativeIn) {
msgFee = msg.value - _amountIn;
}
// transfer through bridge
bytes32 transferId = _transfer(id, _dstTransferSwapper, _desc, _dstSwaps, amountOut, _tokenOut, msgFee);
emit RequestSent(id, transferId, _desc.dstChainId, _amountIn, _tokenIn, _desc.dstTokenOut);
}
function _transfer(
bytes32 _id,
address _dstTransferSwapper,
TransferDescription memory _desc,
ICodec.SwapDescription[] memory _dstSwaps,
uint256 _amount,
address _token,
uint256 _msgFee
) private returns (bytes32 transferId) {
bytes memory requestMessage = _encodeRequestMessage(_id, _desc, _dstSwaps);
transferId = MessageSenderLib.sendMessageWithTransfer(
_dstTransferSwapper,
_token,
_amount,
_desc.dstChainId,
_desc.nonce,
_desc.maxBridgeSlippage,
requestMessage,
_desc.bridgeType,
messageBus,
_msgFee
);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Destination chain functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* @notice Executes a swap if needed, then sends the output token to the receiver
* @dev If allowPartialFill is off, this function reverts as soon as one swap in swap routes fails
* @dev This function is called and is only callable by MessageBus. The transaction of such call is triggered by executor.
* @param _token the token received by this contract
* @param _amount the amount of token received by this contract
* @return ok whether the processing is successful
*/
function executeMessageWithTransfer(
address, // _sender
address _token,
uint256 _amount,
uint64, // _srcChainId
bytes memory _message,
address // _executor
) external payable override onlyMessageBus nonReentrant returns (ExecutionStatus) {
Request memory m = abi.decode((_message), (Request));
// handle the case where amount received is not enough to pay fee
if (_amount < m.fee) {
m.fee = _amount;
emit RequestDone(m.id, 0, 0, _token, m.fee, RequestStatus.Succeeded);
return ExecutionStatus.Success;
} else {
_amount = _amount - m.fee;
}
address tokenOut = _token;
bool nativeOut = m.nativeOut;
uint256 sumAmtOut = _amount;
uint256 sumAmtFailed;
if (m.swaps.length != 0) {
ICodec[] memory codecs;
address tokenIn;
// swap first before sending the token out to user
(, tokenIn, tokenOut, codecs) = sanitizeSwaps(m.swaps);
require(tokenIn == _token, "tkin mm"); // tokenIn mismatch
(sumAmtOut, sumAmtFailed) = executeSwapsWithOverride(m.swaps, codecs, _amount, m.allowPartialFill);
// if at this stage the tx is not reverted, it means at least 1 swap in routes succeeded
}
if (sumAmtFailed > 0) {
_sendToken(_token, sumAmtFailed, m.receiver, false);
}
_sendToken(tokenOut, sumAmtOut, m.receiver, nativeOut);
// status is always success as long as this function call doesn't revert. partial fill is also considered success
emit RequestDone(m.id, sumAmtOut, sumAmtFailed, _token, m.fee, RequestStatus.Succeeded);
return ExecutionStatus.Success;
}
/**
* @notice Sends the received token to the receiver
* @dev Only called if executeMessageWithTransfer reverts
* @param _token the token received by this contract
* @param _amount the amount of token received by this contract
* @return ok whether the processing is successful
*/
function executeMessageWithTransferFallback(
address, // _sender
address _token,
uint256 _amount,
uint64, // _srcChainId
bytes memory _message,
address // _executor
) external payable override onlyMessageBus nonReentrant returns (ExecutionStatus) {
Request memory m = abi.decode((_message), (Request));
uint256 refundAmount = _amount - m.fee; // no need to check amount >= fee as it's already checked before
_sendToken(_token, refundAmount, m.receiver, false);
emit RequestDone(m.id, 0, refundAmount, _token, m.fee, RequestStatus.Fallback);
return ExecutionStatus.Success;
}
/**
* @notice Used to trigger refund when bridging fails due to large slippage
* @dev only MessageBus can call this function, this requires the user to get sigs of the message from SGN
* @param _token the token received by this contract
* @param _amount the amount of token received by this contract
* @return ok whether the processing is successful
*/
function executeMessageWithTransferRefund(
address _token,
uint256 _amount,
bytes calldata _message,
address // _executor
) external payable override onlyMessageBus nonReentrant returns (ExecutionStatus) {
Request memory m = abi.decode((_message), (Request));
_sendToken(_token, _amount, m.receiver, false);
emit RequestDone(m.id, 0, _amount, _token, m.fee, RequestStatus.Fallback);
return ExecutionStatus.Success;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Misc
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function _computeId(address _receiver, uint64 _nonce) private view returns (bytes32) {
return keccak256(abi.encodePacked(msg.sender, _receiver, uint64(block.chainid), _nonce));
}
function _sendToken(
address _token,
uint256 _amount,
address _receiver,
bool _nativeOut
) private {
if (_nativeOut) {
require(_token == nativeWrap, "tk no native");
IWETH(nativeWrap).withdraw(_amount);
(bool sent, ) = _receiver.call{value: _amount, gas: 50000}("");
require(sent, "send fail");
} else {
IERC20(_token).safeTransfer(_receiver, _amount);
}
}
function _encodeRequestMessage(
bytes32 _id,
TransferDescription memory _desc,
ICodec.SwapDescription[] memory _swaps
) private pure returns (bytes memory message) {
message = abi.encode(
Request({
id: _id,
swaps: _swaps,
receiver: _desc.receiver,
nativeOut: _desc.nativeOut,
fee: _desc.fee,
allowPartialFill: _desc.allowPartialFill
})
);
}
function _verifyFee(
TransferDescription memory _desc,
uint256 _amountIn,
address _tokenIn
) private view {
bytes32 hash = keccak256(
abi.encodePacked(
"executor fee",
uint64(block.chainid),
_desc.dstChainId,
_amountIn,
_tokenIn,
_desc.feeDeadline,
_desc.fee
)
);
bytes32 signHash = hash.toEthSignedMessageHash();
verifySig(signHash, _desc.feeSig);
require(_desc.feeDeadline > block.timestamp, "deadline exceeded");
}
function setNativeWrap(address _nativeWrap) external onlyOwner {
nativeWrap = _nativeWrap;
emit NativeWrapUpdated(_nativeWrap);
}
// This is needed to receive ETH when calling `IWETH.withdraw`
receive() external payable {}
}
{
"compilationTarget": {
"contracts/TransferSwapper.sol": "TransferSwapper"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"details": {
"constantOptimizer": true,
"cse": true,
"deduplicate": true,
"inliner": true,
"jumpdestRemover": true,
"orderLiterals": true,
"peephole": true,
"yul": false
},
"runs": 800
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_messageBus","type":"address"},{"internalType":"address","name":"_nativeWrap","type":"address"},{"internalType":"address","name":"_signer","type":"address"},{"internalType":"address","name":"_feeCollector","type":"address"},{"internalType":"string[]","name":"_funcSigs","type":"string[]"},{"internalType":"address[]","name":"_codecs","type":"address[]"},{"internalType":"address[]","name":"_supportedDexList","type":"address[]"},{"internalType":"string[]","name":"_supportedDexFuncs","type":"string[]"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes4","name":"selector","type":"bytes4"},{"indexed":false,"internalType":"address","name":"codec","type":"address"}],"name":"CodecUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"address","name":"tokenIn","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"},{"indexed":false,"internalType":"address","name":"tokenOut","type":"address"}],"name":"DirectSwap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"}],"name":"FeeCollectorUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"messageBus","type":"address"}],"name":"MessageBusUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"nativeWrap","type":"address"}],"name":"NativeWrapUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"dstAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"refundAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"refundToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"feeCollected","type":"uint256"},{"indexed":false,"internalType":"enum TransferSwapper.RequestStatus","name":"status","type":"uint8"}],"name":"RequestDone","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"transferId","type":"bytes32"},{"indexed":false,"internalType":"uint64","name":"dstChainId","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"srcAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"srcToken","type":"address"},{"indexed":false,"internalType":"address","name":"dstToken","type":"address"}],"name":"RequestSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"}],"name":"SignerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"dex","type":"address"},{"indexed":false,"internalType":"bytes4","name":"selector","type":"bytes4"},{"indexed":false,"internalType":"bool","name":"enabled","type":"bool"}],"name":"SupportedDexUpdated","type":"event"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"codecs","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_tokens","type":"address[]"},{"internalType":"address","name":"_to","type":"address"}],"name":"collectFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"dexRegistry","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"uint64","name":"_srcChainId","type":"uint64"},{"internalType":"bytes","name":"_message","type":"bytes"},{"internalType":"address","name":"_executor","type":"address"}],"name":"executeMessage","outputs":[{"internalType":"enum IMessageReceiverApp.ExecutionStatus","name":"","type":"uint8"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint64","name":"","type":"uint64"},{"internalType":"bytes","name":"_message","type":"bytes"},{"internalType":"address","name":"","type":"address"}],"name":"executeMessageWithTransfer","outputs":[{"internalType":"enum IMessageReceiverApp.ExecutionStatus","name":"","type":"uint8"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint64","name":"","type":"uint64"},{"internalType":"bytes","name":"_message","type":"bytes"},{"internalType":"address","name":"","type":"address"}],"name":"executeMessageWithTransferFallback","outputs":[{"internalType":"enum IMessageReceiverApp.ExecutionStatus","name":"","type":"uint8"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_message","type":"bytes"},{"internalType":"address","name":"","type":"address"}],"name":"executeMessageWithTransferRefund","outputs":[{"internalType":"enum IMessageReceiverApp.ExecutionStatus","name":"","type":"uint8"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"feeCollector","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"messageBus","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nativeWrap","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"selector2codec","outputs":[{"internalType":"contract ICodec","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_funcSig","type":"string"},{"internalType":"address","name":"_codec","type":"address"}],"name":"setCodec","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_feeCollector","type":"address"}],"name":"setFeeCollector","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_messageBus","type":"address"}],"name":"setMessageBus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_nativeWrap","type":"address"}],"name":"setNativeWrap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_signer","type":"address"}],"name":"setSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_dex","type":"address"},{"internalType":"bytes4","name":"_selector","type":"bytes4"},{"internalType":"bool","name":"_enabled","type":"bool"}],"name":"setSupportedDex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"signer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_dstTransferSwapper","type":"address"},{"components":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint64","name":"dstChainId","type":"uint64"},{"internalType":"uint32","name":"maxBridgeSlippage","type":"uint32"},{"internalType":"enum MsgDataTypes.BridgeSendType","name":"bridgeType","type":"uint8"},{"internalType":"uint64","name":"nonce","type":"uint64"},{"internalType":"bool","name":"nativeIn","type":"bool"},{"internalType":"bool","name":"nativeOut","type":"bool"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"uint256","name":"feeDeadline","type":"uint256"},{"internalType":"bytes","name":"feeSig","type":"bytes"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"dstTokenOut","type":"address"},{"internalType":"bool","name":"allowPartialFill","type":"bool"}],"internalType":"struct TransferSwapper.TransferDescription","name":"_desc","type":"tuple"},{"components":[{"internalType":"address","name":"dex","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ICodec.SwapDescription[]","name":"_srcSwaps","type":"tuple[]"},{"components":[{"internalType":"address","name":"dex","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct ICodec.SwapDescription[]","name":"_dstSwaps","type":"tuple[]"}],"name":"transferWithSwap","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]