Contract Name:
MultichainEndpoint
Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(address from, address to, uint256 amount) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @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);
/**
* @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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.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;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
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));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
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");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @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");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @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).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// 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 cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [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://consensys.net/diligence/blog/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.8.0/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 functionCallWithValue(target, data, 0, "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");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, 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) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, 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) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or 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 {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// 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
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// 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: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol)
pragma solidity ^0.8.0;
// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.
/**
* @dev Wrappers over Solidity's arithmetic operations.
*
* NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
* now has built in overflow checking.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator.
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b <= a, errorMessage);
return a - b;
}
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a / b;
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a % b;
}
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract CrossChainERC20 is IERC20 {
struct CrossChainERC20Config {
string name;
string symbol;
uint8 decimals;
uint64 originalChainId;
address originalAddress;
}
mapping(address => uint256) internal _balances;
mapping(address => mapping(address => uint256)) internal _allowances;
string public name;
string public symbol;
uint8 public decimals;
address public originalAddress;
uint64 public originalChainId;
address public immutable deployer;
uint256 public totalSupply;
constructor(CrossChainERC20Config memory _config) {
name = _config.name;
symbol = _config.symbol;
decimals = _config.decimals;
originalChainId = _config.originalChainId;
originalAddress = _config.originalAddress;
deployer = msg.sender;
}
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
function _approve(
address owner,
address spender,
uint256 amount
) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
}
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = msg.sender;
_approve(owner, spender, amount);
return true;
}
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
uint256 currentAllowance = _allowances[owner][spender];
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
}
_balances[to] += amount;
}
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = msg.sender;
_transfer(owner, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
address spender = msg.sender;
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
function mint(address to, uint256 amount) public {
require(msg.sender == deployer);
_balances[to] += amount;
totalSupply += amount;
}
function burn(address from, uint256 amount) public {
require(msg.sender == deployer);
_balances[from] -= amount;
totalSupply -= amount;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./CrossChainERC20.sol";
import "./ICrossChainVault.sol";
import "../op/IMultichainEndpoint.sol";
import "../op/celer/message/framework/MessageApp.sol";
contract CrossChainVaultApp is MessageApp, Ownable {
using SafeERC20 for ERC20;
enum CrossChainVaultMessageType {
LockAndMint,
BurnAndUnlock
}
struct SetAllowedSender {
address sender;
uint64 srcChainId;
bool isAllowed;
}
struct CrossChainAssetData {
bool isDataSet;
address token;
string name;
string symbol;
uint8 decimals;
uint64 chainId;
}
event SetAuthorisedSender(address indexed sender, uint64 indexed chainId, bool isSet);
event ActualEndpointChange(address prev, address updated);
event EndpointExecutionReverted(bytes reason);
ICrossChainVault public immutable vault;
IMultichainEndpoint public endpoint;
mapping(uint64 => mapping(address => CrossChainERC20)) public mintedTokenByOriginal;
mapping(uint64 => mapping(CrossChainERC20 => address)) public originalTokenByMinted;
mapping(address => mapping(uint64 => bool)) public allowedSenders;
mapping(uint64 => bool) public allowedSenderSetup;
mapping(address => mapping(uint64 => CrossChainAssetData)) public crossChainAssetsData;
constructor(address _vault, address _messageBus) MessageApp(_messageBus) {
vault = ICrossChainVault(_vault);
}
function setActualEndpoint(address _endpoint) public onlyOwner {
require(address(endpoint) == address(0), "The endpoint is already configured");
emit ActualEndpointChange(address(endpoint), _endpoint);
endpoint = IMultichainEndpoint(_endpoint);
}
function setCrossChainAssets(CrossChainAssetData[] calldata assets) public onlyOwner {
for (uint i = 0; i < assets.length; i++) {
crossChainAssetsData[assets[i].token][assets[i].chainId] = assets[i];
}
}
function setAllowedSenders(SetAllowedSender[] calldata senders) public onlyOwner {
for (uint i = 0; i < senders.length; i++) {
// We can't add new senders for same chain id otherwise it would be dangerous
require(!allowedSenderSetup[senders[i].srcChainId], "Sender is already setup");
emit SetAuthorisedSender(senders[i].sender, senders[i].srcChainId, senders[i].isAllowed);
allowedSenders[senders[i].sender][senders[i].srcChainId] = senders[i].isAllowed;
allowedSenderSetup[senders[i].srcChainId] = true;
}
}
function executeMessage(
address srcContract,
uint64 _srcChainId,
bytes calldata _message,
address
) external payable override onlyMessageBus returns (ExecutionStatus) {
require(allowedSenders[srcContract][_srcChainId], "Unauthorised sender");
(bytes memory lockData, bytes memory _data) = abi.decode(_message, (bytes, bytes));
(address token, uint256 amount) = _processVaultCommand(_srcChainId, lockData);
ERC20(token).safeTransfer(address(endpoint), amount);
IMultichainEndpoint.CallbackExecutionStatus status = IMultichainEndpoint.CallbackExecutionStatus.Failed;
try endpoint.executeMessageWithTransfer(
token,
amount,
_srcChainId,
_data
) returns (IMultichainEndpoint.CallbackExecutionStatus _status) {
status = _status;
} catch (bytes memory reason) {
emit EndpointExecutionReverted(reason);
status = endpoint.executeMessageWithTransferFallback(
token,
amount,
_data
);
}
if (status == IMultichainEndpoint.CallbackExecutionStatus.Success) {
return ExecutionStatus.Success;
} else if (status == IMultichainEndpoint.CallbackExecutionStatus.Failed) {
return ExecutionStatus.Fail;
} else if (status == IMultichainEndpoint.CallbackExecutionStatus.Retry) {
return ExecutionStatus.Retry;
}
return ExecutionStatus.Fail;
}
function _processVaultCommand(uint64 srcChainId, bytes memory rawCommand) private returns (address token, uint256 amt) {
(uint8 _type, address srcAddress, uint256 amount) = abi.decode(rawCommand, (uint8, address, uint256));
CrossChainVaultMessageType cmdType = CrossChainVaultMessageType(_type);
amt = amount;
if (cmdType == CrossChainVaultMessageType.LockAndMint) {
CrossChainAssetData memory data = crossChainAssetsData[srcAddress][srcChainId];
require(data.isDataSet, "Metadata is not set");
token = _mintTokens(CrossChainERC20.CrossChainERC20Config(
data.name,
data.symbol,
data.decimals,
srcChainId,
srcAddress
), amount);
} else if (cmdType == CrossChainVaultMessageType.BurnAndUnlock) {
vault.unlock(srcAddress, amount);
token = srcAddress;
} else {
revert();
}
}
function _mintTokens(CrossChainERC20.CrossChainERC20Config memory originalTokenDetails, uint256 amount) private returns (address) {
CrossChainERC20 minted = mintedTokenByOriginal[originalTokenDetails.originalChainId][originalTokenDetails.originalAddress];
if (address(minted) == address(0)) {
minted = new CrossChainERC20(originalTokenDetails);
mintedTokenByOriginal[originalTokenDetails.originalChainId][originalTokenDetails.originalAddress] = minted;
originalTokenByMinted[originalTokenDetails.originalChainId][minted] = originalTokenDetails.originalAddress;
}
minted.mint(address(this), amount);
return address(minted);
}
receive() external payable {}
function lockAndMint(address to, uint64 chainId, address token, uint256 amount, bytes memory message) public payable {
require(msg.sender == address(endpoint), "Invalid endpoint");
ERC20(token).safeTransferFrom(msg.sender, address(this), amount);
ERC20(token).safeIncreaseAllowance(address(vault), amount);
amount = vault.lock(token, amount);
bytes memory lockData = abi.encode(uint8(CrossChainVaultMessageType.LockAndMint), token, amount);
bytes memory payload = abi.encode(lockData, message);
sendMessage(to, chainId, payload, IMessageBus(messageBus).calcFee(payload));
}
function burnAndUnlock(address to, uint64 chainId, address token, uint256 amount, bytes memory message) public payable {
require(msg.sender == address(endpoint), "Invalid endpoint");
require(originalTokenByMinted[chainId][CrossChainERC20(token)] != address(0), "Invalid cross-chain token");
ERC20(token).safeTransferFrom(msg.sender, address(this), amount);
CrossChainERC20 crossChainToken = CrossChainERC20(token);
crossChainToken.burn(address(this), amount);
bytes memory unlockData = abi.encode(uint8(CrossChainVaultMessageType.BurnAndUnlock), crossChainToken.originalAddress(), amount);
bytes memory payload = abi.encode(unlockData, message);
sendMessage(to, chainId, payload, IMessageBus(messageBus).calcFee(payload));
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
interface ICrossChainVault {
function lock(address asset, uint256 amount) external returns (uint256);
function unlock(address asset, uint256 amount) external;
}
// 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 sendNative(
address _receiver,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage
) external payable;
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.17;
interface IDelayedTransfer {
struct delayedTransfer {
address receiver;
address token;
uint256 amount;
uint256 timestamp;
}
function delayedTransfers(bytes32 transferId) external view returns (delayedTransfer memory);
}
// 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;
/**
* @notice Lock native token as original token to trigger mint at a remote chain's PeggedTokenBridge
* @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 depositNative(
uint256 _amount,
uint64 _mintChainId,
address _mintAccount,
uint64 _nonce
) external payable;
/**
* @notice Withdraw locked original tokens triggered by a burn at a remote chain's PeggedTokenBridge.
* @param _request The serialized Withdraw 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 bridge's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function withdraw(
bytes calldata _request,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) 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);
/**
* @notice Lock native token as original token to trigger mint at a remote chain's PeggedTokenBridge
* @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 depositNative(
uint256 _amount,
uint64 _mintChainId,
address _mintAccount,
uint64 _nonce
) external payable returns (bytes32);
/**
* @notice Withdraw locked original tokens triggered by a burn at a remote chain's PeggedTokenBridge.
* @param _request The serialized Withdraw 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 bridge's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function withdraw(
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.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;
/**
* @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;
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.0;
interface ISigsVerifier {
/**
* @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;
library Utils {
// https://ethereum.stackexchange.com/a/83577
// https://github.com/Uniswap/v3-periphery/blob/v1.0.0/contracts/base/Multicall.sol
function getRevertMsg(bytes memory _returnData) internal pure returns (string memory) {
// If the _res length is less than 68, then the transaction failed silently (without a revert message)
if (_returnData.length < 68) return "Transaction reverted silently";
assembly {
// Slice the sighash.
_returnData := add(_returnData, 0x04)
}
return abi.decode(_returnData, (string)); // All that remains is the revert string
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
import "./MessageSenderApp.sol";
import "./MessageReceiverApp.sol";
abstract contract MessageApp is MessageSenderApp, MessageReceiverApp {
constructor(address _messageBus) {
messageBus = _messageBus;
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
abstract contract MessageBusAddress {
address public messageBus;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
import "../interfaces/IMessageReceiverApp.sol";
import "../libraries/MsgDataTypes.sol";
import "./MessageBusAddress.sol";
abstract contract MessageReceiverApp is IMessageReceiverApp, MessageBusAddress {
modifier onlyMessageBus() {
require(msg.sender == messageBus, "caller is not message bus");
_;
}
// Add abort prefix in the reason string for require or revert.
// This will abort (revert) the message execution without markig it as failed state,
// making it possible to retry later.
function _abortReason(string memory reason) internal pure returns (string memory) {
return MsgDataTypes.abortReason(reason);
}
/**
* @notice Called by MessageBus to execute a message
* @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) {}
// execute message from non-evm chain with bytes for sender address,
// otherwise same as above.
function executeMessage(
bytes calldata _sender,
uint64 _srcChainId,
bytes calldata _message,
address _executor
) external payable virtual override onlyMessageBus returns (ExecutionStatus) {}
/**
* @notice Called by MessageBus to execute a message with an associated token transfer.
* The contract is guaranteed to have received the right amount of tokens before this function is called.
* @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.
* @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 if
* 1. executeMessageWithTransfer reverts, or
* 2. executeMessageWithTransfer returns ExecutionStatus.Fail
* The contract is guaranteed to have received the right amount of tokens before this function is called.
* @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.
* @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 to process refund of the original transfer from this contract.
* The contract is guaranteed to have received the refund before this function is called.
* @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) {}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../libraries/MsgDataTypes.sol";
import "../libraries/MessageSenderLib.sol";
import "../messagebus/MessageBus.sol";
import "./MessageBusAddress.sol";
abstract contract MessageSenderApp is MessageBusAddress {
using SafeERC20 for IERC20;
// ============== Utility functions called by apps ==============
/**
* @notice Sends a message to a contract on another chain.
* Sender needs to make sure the uniqueness of the message Id, which is computed as
* hash(type.MessageOnly, sender, receiver, srcChainId, srcTxHash, dstChainId, message).
* If messages with the same Id are sent, only one of them will succeed at dst chain.
* @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 _fee The fee amount to pay to MessageBus.
*/
function sendMessage(
address _receiver,
uint64 _dstChainId,
bytes memory _message,
uint256 _fee
) internal {
MessageSenderLib.sendMessage(_receiver, _dstChainId, _message, messageBus, _fee);
}
// Send message to non-evm chain with bytes for receiver address,
// otherwise same as above.
function sendMessage(
bytes calldata _receiver,
uint64 _dstChainId,
bytes memory _message,
uint256 _fee
) internal {
MessageSenderLib.sendMessage(_receiver, _dstChainId, _message, messageBus, _fee);
}
/**
* @notice Sends a message associated with a transfer to a contract on another chain.
* @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.
* If message is empty, only the token transfer will be sent
* @param _bridgeSendType One of the {BridgeSendType} enum.
* @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,
uint256 _fee
) internal returns (bytes32) {
return
MessageSenderLib.sendMessageWithTransfer(
_receiver,
_token,
_amount,
_dstChainId,
_nonce,
_maxSlippage,
_message,
_bridgeSendType,
messageBus,
_fee
);
}
/**
* @notice Sends a token transfer via a bridge.
* @dev sendMessageWithTransfer with empty message
* @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 _bridgeSendType One of the {BridgeSendType} enum.
*/
function sendTokenTransfer(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage,
MsgDataTypes.BridgeSendType _bridgeSendType
) internal returns (bytes32) {
return
MessageSenderLib.sendMessageWithTransfer(
_receiver,
_token,
_amount,
_dstChainId,
_nonce,
_maxSlippage,
"", // empty message, which will not trigger sendMessage
_bridgeSendType,
messageBus,
0
);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
import "../libraries/MsgDataTypes.sol";
interface IMessageBus {
/**
* @notice Send a message to a contract on another chain.
* Sender needs to make sure the uniqueness of the message Id, which is computed as
* hash(type.MessageOnly, sender, receiver, srcChainId, srcTxHash, dstChainId, message).
* If messages with the same Id are sent, only one of them will succeed at dst chain..
* 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;
// same as above, except that receiver is an non-evm chain address,
function sendMessage(
bytes calldata _receiver,
uint256 _dstChainId,
bytes calldata _message
) external payable;
/**
* @notice Send a message associated with a token transfer to a contract on another chain.
* If messages with the same srcTransferId are sent, only one of them will succeed at dst chain..
* 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 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;
/**
* @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 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 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);
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);
}
// 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 to execute a message
* @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);
// same as above, except that sender is an non-evm chain address,
// otherwise same as above.
function executeMessage(
bytes calldata _sender,
uint64 _srcChainId,
bytes calldata _message,
address _executor
) external payable returns (ExecutionStatus);
/**
* @notice Called by MessageBus to execute a message with an associated token transfer.
* The contract is guaranteed to have received the right amount of tokens before this function is called.
* @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.
* @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 if
* 1. executeMessageWithTransfer reverts, or
* 2. executeMessageWithTransfer returns ExecutionStatus.Fail
* The contract is guaranteed to have received the right amount of tokens before this function is called.
* @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.
* @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 to process refund of the original transfer from this contract.
* The contract is guaranteed to have received the refund before this function is called.
* @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);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
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);
}
// Send message to non-evm chain with bytes for receiver address,
// otherwise same as above.
function sendMessage(
bytes calldata _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) {
(bytes32 transferId, address bridge) = sendTokenTransfer(
_receiver,
_token,
_amount,
_dstChainId,
_nonce,
_maxSlippage,
_bridgeSendType,
_messageBus
);
if (_message.length > 0) {
IMessageBus(_messageBus).sendMessageWithTransfer{value: _fee}(
_receiver,
_dstChainId,
bridge,
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 _messageBus
) internal returns (bytes32 transferId, address bridge) {
if (_bridgeSendType == MsgDataTypes.BridgeSendType.Liquidity) {
bridge = IMessageBus(_messageBus).liquidityBridge();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
IBridge(bridge).send(_receiver, _token, _amount, _dstChainId, _nonce, _maxSlippage);
transferId = computeLiqBridgeTransferId(_receiver, _token, _amount, _dstChainId, _nonce);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegDeposit) {
bridge = IMessageBus(_messageBus).pegVault();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
IOriginalTokenVault(bridge).deposit(_token, _amount, _dstChainId, _receiver, _nonce);
transferId = computePegV1DepositId(_receiver, _token, _amount, _dstChainId, _nonce);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegBurn) {
bridge = IMessageBus(_messageBus).pegBridge();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
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);
transferId = computePegV1BurnId(_receiver, _token, _amount, _nonce);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegV2Deposit) {
bridge = IMessageBus(_messageBus).pegVaultV2();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
transferId = IOriginalTokenVaultV2(bridge).deposit(_token, _amount, _dstChainId, _receiver, _nonce);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegV2Burn) {
bridge = IMessageBus(_messageBus).pegBridgeV2();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
transferId = 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) {
bridge = IMessageBus(_messageBus).pegBridgeV2();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
transferId = 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");
}
}
function computeLiqBridgeTransferId(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce
) internal view returns (bytes32) {
return
keccak256(
abi.encodePacked(address(this), _receiver, _token, _amount, _dstChainId, _nonce, uint64(block.chainid))
);
}
function computePegV1DepositId(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce
) internal view returns (bytes32) {
return
keccak256(
abi.encodePacked(address(this), _token, _amount, _dstChainId, _receiver, _nonce, uint64(block.chainid))
);
}
function computePegV1BurnId(
address _receiver,
address _token,
uint256 _amount,
uint64 _nonce
) internal view returns (bytes32) {
return keccak256(abi.encodePacked(address(this), _token, _amount, _receiver, _nonce, uint64(block.chainid)));
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
library MsgDataTypes {
string constant ABORT_PREFIX = "MSG::ABORT:";
// Add abort prefix in the reason string for require or revert.
// This will abort (revert) the message execution without markig it as failed state,
// making it possible to retry later.
function abortReason(string memory reason) internal pure returns (string memory) {
return string.concat(MsgDataTypes.ABORT_PREFIX, reason);
}
// 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
}
// used for msg from non-evm chains with longer-bytes address
struct RouteInfo2 {
bytes sender;
address receiver;
uint64 srcChainId;
bytes32 srcTxHash;
}
// combination of RouteInfo and RouteInfo2 for easier processing
struct Route {
address sender; // from RouteInfo
bytes senderBytes; // from RouteInfo2
address receiver;
uint64 srcChainId;
bytes32 srcTxHash;
}
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: GPL-3.0-only
pragma solidity 0.8.17;
import "./MessageBusSender.sol";
import "./MessageBusReceiver.sol";
contract MessageBus is MessageBusSender, MessageBusReceiver {
constructor(
ISigsVerifier _sigsVerifier,
address _liquidityBridge,
address _pegBridge,
address _pegVault,
address _pegBridgeV2,
address _pegVaultV2
)
MessageBusSender(_sigsVerifier)
MessageBusReceiver(_liquidityBridge, _pegBridge, _pegVault, _pegBridgeV2, _pegVaultV2)
{}
// this is only to be called by Proxy via delegateCall as initOwner will require _owner is 0.
// so calling init on this contract directly will guarantee to fail
function init(
address _liquidityBridge,
address _pegBridge,
address _pegVault,
address _pegBridgeV2,
address _pegVaultV2
) external {
// MUST manually call ownable init and must only call once
initOwner();
// we don't need sender init as _sigsVerifier is immutable so already in the deployed code
initReceiver(_liquidityBridge, _pegBridge, _pegVault, _pegBridgeV2, _pegVaultV2);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.17;
import "../libraries/MsgDataTypes.sol";
import "../interfaces/IMessageReceiverApp.sol";
import "../../interfaces/IBridge.sol";
import "../../interfaces/IOriginalTokenVault.sol";
import "../../interfaces/IOriginalTokenVaultV2.sol";
import "../../interfaces/IPeggedTokenBridge.sol";
import "../../interfaces/IPeggedTokenBridgeV2.sol";
import "../../interfaces/IDelayedTransfer.sol";
import "../../safeguard/Ownable.sol";
import "../../libraries/Utils.sol";
contract MessageBusReceiver is Ownable {
mapping(bytes32 => MsgDataTypes.TxStatus) public executedMessages;
address public liquidityBridge; // liquidity bridge address
address public pegBridge; // peg bridge address
address public pegVault; // peg original vault address
address public pegBridgeV2; // peg bridge address
address public pegVaultV2; // peg original vault address
// minimum amount of gas needed by this contract before it tries to
// deliver a message to the target contract.
uint256 public preExecuteMessageGasUsage;
event Executed(
MsgDataTypes.MsgType msgType,
bytes32 msgId,
MsgDataTypes.TxStatus status,
address indexed receiver,
uint64 srcChainId,
bytes32 srcTxHash
);
event NeedRetry(MsgDataTypes.MsgType msgType, bytes32 msgId, uint64 srcChainId, bytes32 srcTxHash);
event CallReverted(string reason); // help debug
event LiquidityBridgeUpdated(address liquidityBridge);
event PegBridgeUpdated(address pegBridge);
event PegVaultUpdated(address pegVault);
event PegBridgeV2Updated(address pegBridgeV2);
event PegVaultV2Updated(address pegVaultV2);
constructor(
address _liquidityBridge,
address _pegBridge,
address _pegVault,
address _pegBridgeV2,
address _pegVaultV2
) {
liquidityBridge = _liquidityBridge;
pegBridge = _pegBridge;
pegVault = _pegVault;
pegBridgeV2 = _pegBridgeV2;
pegVaultV2 = _pegVaultV2;
}
function initReceiver(
address _liquidityBridge,
address _pegBridge,
address _pegVault,
address _pegBridgeV2,
address _pegVaultV2
) internal {
require(liquidityBridge == address(0), "liquidityBridge already set");
liquidityBridge = _liquidityBridge;
pegBridge = _pegBridge;
pegVault = _pegVault;
pegBridgeV2 = _pegBridgeV2;
pegVaultV2 = _pegVaultV2;
}
// ============== functions called by executor ==============
/**
* @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
) public payable {
// For message with token transfer, message Id is computed through transfer info
// in order to guarantee that each transfer can only be used once.
bytes32 messageId = verifyTransfer(_transfer);
require(executedMessages[messageId] == MsgDataTypes.TxStatus.Null, "transfer already executed");
executedMessages[messageId] = MsgDataTypes.TxStatus.Pending;
bytes32 domain = keccak256(abi.encodePacked(block.chainid, address(this), "MessageWithTransfer"));
IBridge(liquidityBridge).verifySigs(
abi.encodePacked(domain, messageId, _message, _transfer.srcTxHash),
_sigs,
_signers,
_powers
);
MsgDataTypes.TxStatus status;
IMessageReceiverApp.ExecutionStatus est = executeMessageWithTransfer(_transfer, _message);
if (est == IMessageReceiverApp.ExecutionStatus.Success) {
status = MsgDataTypes.TxStatus.Success;
} else if (est == IMessageReceiverApp.ExecutionStatus.Retry) {
executedMessages[messageId] = MsgDataTypes.TxStatus.Null;
emit NeedRetry(
MsgDataTypes.MsgType.MessageWithTransfer,
messageId,
_transfer.srcChainId,
_transfer.srcTxHash
);
return;
} else {
est = executeMessageWithTransferFallback(_transfer, _message);
if (est == IMessageReceiverApp.ExecutionStatus.Success) {
status = MsgDataTypes.TxStatus.Fallback;
} else {
status = MsgDataTypes.TxStatus.Fail;
}
}
executedMessages[messageId] = status;
emitMessageWithTransferExecutedEvent(messageId, status, _transfer);
}
/**
* @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
) public payable {
// similar to executeMessageWithTransfer
bytes32 messageId = verifyTransfer(_transfer);
require(executedMessages[messageId] == MsgDataTypes.TxStatus.Null, "transfer already executed");
executedMessages[messageId] = MsgDataTypes.TxStatus.Pending;
bytes32 domain = keccak256(abi.encodePacked(block.chainid, address(this), "MessageWithTransferRefund"));
IBridge(liquidityBridge).verifySigs(
abi.encodePacked(domain, messageId, _message, _transfer.srcTxHash),
_sigs,
_signers,
_powers
);
MsgDataTypes.TxStatus status;
IMessageReceiverApp.ExecutionStatus est = executeMessageWithTransferRefund(_transfer, _message);
if (est == IMessageReceiverApp.ExecutionStatus.Success) {
status = MsgDataTypes.TxStatus.Success;
} else if (est == IMessageReceiverApp.ExecutionStatus.Retry) {
executedMessages[messageId] = MsgDataTypes.TxStatus.Null;
emit NeedRetry(
MsgDataTypes.MsgType.MessageWithTransfer,
messageId,
_transfer.srcChainId,
_transfer.srcTxHash
);
return;
} else {
status = MsgDataTypes.TxStatus.Fail;
}
executedMessages[messageId] = status;
emitMessageWithTransferExecutedEvent(messageId, status, _transfer);
}
/**
* @notice Execute a message not associated with a transfer.
* @param _message Arbitrary message bytes originated from and encoded by the source app contract
* @param _route The info about the sender and the receiver.
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by
* +2/3 of the sigsVerifier's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function executeMessage(
bytes calldata _message,
MsgDataTypes.RouteInfo calldata _route,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external payable {
MsgDataTypes.Route memory route = getRouteInfo(_route);
executeMessage(_message, route, _sigs, _signers, _powers, "Message");
}
// execute message from non-evm chain with bytes for sender address,
// otherwise same as above.
function executeMessage(
bytes calldata _message,
MsgDataTypes.RouteInfo2 calldata _route,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external payable {
MsgDataTypes.Route memory route = getRouteInfo(_route);
executeMessage(_message, route, _sigs, _signers, _powers, "Message2");
}
function executeMessage(
bytes calldata _message,
MsgDataTypes.Route memory _route,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers,
string memory domainName
) private {
// For message without associated token transfer, message Id is computed through message info,
// in order to guarantee that each message can only be applied once
bytes32 messageId = computeMessageOnlyId(_route, _message);
require(executedMessages[messageId] == MsgDataTypes.TxStatus.Null, "message already executed");
executedMessages[messageId] = MsgDataTypes.TxStatus.Pending;
bytes32 domain = keccak256(abi.encodePacked(block.chainid, address(this), domainName));
IBridge(liquidityBridge).verifySigs(abi.encodePacked(domain, messageId), _sigs, _signers, _powers);
MsgDataTypes.TxStatus status;
IMessageReceiverApp.ExecutionStatus est = executeMessage(_route, _message);
if (est == IMessageReceiverApp.ExecutionStatus.Success) {
status = MsgDataTypes.TxStatus.Success;
} else if (est == IMessageReceiverApp.ExecutionStatus.Retry) {
executedMessages[messageId] = MsgDataTypes.TxStatus.Null;
emit NeedRetry(MsgDataTypes.MsgType.MessageOnly, messageId, _route.srcChainId, _route.srcTxHash);
return;
} else {
status = MsgDataTypes.TxStatus.Fail;
}
executedMessages[messageId] = status;
emitMessageOnlyExecutedEvent(messageId, status, _route);
}
// ================= utils (to avoid stack too deep) =================
function emitMessageWithTransferExecutedEvent(
bytes32 _messageId,
MsgDataTypes.TxStatus _status,
MsgDataTypes.TransferInfo calldata _transfer
) private {
emit Executed(
MsgDataTypes.MsgType.MessageWithTransfer,
_messageId,
_status,
_transfer.receiver,
_transfer.srcChainId,
_transfer.srcTxHash
);
}
function emitMessageOnlyExecutedEvent(
bytes32 _messageId,
MsgDataTypes.TxStatus _status,
MsgDataTypes.Route memory _route
) private {
emit Executed(
MsgDataTypes.MsgType.MessageOnly,
_messageId,
_status,
_route.receiver,
_route.srcChainId,
_route.srcTxHash
);
}
function executeMessageWithTransfer(MsgDataTypes.TransferInfo calldata _transfer, bytes calldata _message)
private
returns (IMessageReceiverApp.ExecutionStatus)
{
uint256 gasLeftBeforeExecution = gasleft();
(bool ok, bytes memory res) = address(_transfer.receiver).call{value: msg.value}(
abi.encodeWithSelector(
IMessageReceiverApp.executeMessageWithTransfer.selector,
_transfer.sender,
_transfer.token,
_transfer.amount,
_transfer.srcChainId,
_message,
msg.sender
)
);
if (ok) {
return abi.decode((res), (IMessageReceiverApp.ExecutionStatus));
}
handleExecutionRevert(gasLeftBeforeExecution, res);
return IMessageReceiverApp.ExecutionStatus.Fail;
}
function executeMessageWithTransferFallback(MsgDataTypes.TransferInfo calldata _transfer, bytes calldata _message)
private
returns (IMessageReceiverApp.ExecutionStatus)
{
uint256 gasLeftBeforeExecution = gasleft();
(bool ok, bytes memory res) = address(_transfer.receiver).call{value: msg.value}(
abi.encodeWithSelector(
IMessageReceiverApp.executeMessageWithTransferFallback.selector,
_transfer.sender,
_transfer.token,
_transfer.amount,
_transfer.srcChainId,
_message,
msg.sender
)
);
if (ok) {
return abi.decode((res), (IMessageReceiverApp.ExecutionStatus));
}
handleExecutionRevert(gasLeftBeforeExecution, res);
return IMessageReceiverApp.ExecutionStatus.Fail;
}
function executeMessageWithTransferRefund(MsgDataTypes.TransferInfo calldata _transfer, bytes calldata _message)
private
returns (IMessageReceiverApp.ExecutionStatus)
{
uint256 gasLeftBeforeExecution = gasleft();
(bool ok, bytes memory res) = address(_transfer.receiver).call{value: msg.value}(
abi.encodeWithSelector(
IMessageReceiverApp.executeMessageWithTransferRefund.selector,
_transfer.token,
_transfer.amount,
_message,
msg.sender
)
);
if (ok) {
return abi.decode((res), (IMessageReceiverApp.ExecutionStatus));
}
handleExecutionRevert(gasLeftBeforeExecution, res);
return IMessageReceiverApp.ExecutionStatus.Fail;
}
function verifyTransfer(MsgDataTypes.TransferInfo calldata _transfer) private view returns (bytes32) {
bytes32 transferId;
address bridgeAddr;
MsgDataTypes.TransferType t = _transfer.t;
if (t == MsgDataTypes.TransferType.LqRelay) {
bridgeAddr = liquidityBridge;
transferId = keccak256(
abi.encodePacked(
_transfer.sender,
_transfer.receiver,
_transfer.token,
_transfer.amount,
_transfer.srcChainId,
uint64(block.chainid),
_transfer.refId
)
);
require(IBridge(bridgeAddr).transfers(transferId) == true, "relay not exist");
} else if (t == MsgDataTypes.TransferType.LqWithdraw) {
bridgeAddr = liquidityBridge;
transferId = keccak256(
abi.encodePacked(
uint64(block.chainid),
_transfer.wdseq,
_transfer.receiver,
_transfer.token,
_transfer.amount
)
);
require(IBridge(bridgeAddr).withdraws(transferId) == true, "withdraw not exist");
} else {
if (t == MsgDataTypes.TransferType.PegMint || t == MsgDataTypes.TransferType.PegWithdraw) {
bridgeAddr = (t == MsgDataTypes.TransferType.PegMint) ? pegBridge : pegVault;
transferId = keccak256(
abi.encodePacked(
_transfer.receiver,
_transfer.token,
_transfer.amount,
_transfer.sender,
_transfer.srcChainId,
_transfer.refId
)
);
} else {
bridgeAddr = (t == MsgDataTypes.TransferType.PegV2Mint) ? pegBridgeV2 : pegVaultV2;
transferId = keccak256(
abi.encodePacked(
_transfer.receiver,
_transfer.token,
_transfer.amount,
_transfer.sender,
_transfer.srcChainId,
_transfer.refId,
bridgeAddr
)
);
}
// function is same for peg, peg2, vault, vault2
require(IPeggedTokenBridge(bridgeAddr).records(transferId) == true, "record not exist");
}
require(IDelayedTransfer(bridgeAddr).delayedTransfers(transferId).timestamp == 0, "transfer delayed");
return keccak256(abi.encodePacked(MsgDataTypes.MsgType.MessageWithTransfer, bridgeAddr, transferId));
}
function computeMessageOnlyId(MsgDataTypes.Route memory _route, bytes calldata _message)
private
view
returns (bytes32)
{
bytes memory sender = _route.senderBytes;
if (sender.length == 0) {
sender = abi.encodePacked(_route.sender);
}
return
keccak256(
abi.encodePacked(
MsgDataTypes.MsgType.MessageOnly,
sender,
_route.receiver,
_route.srcChainId,
_route.srcTxHash,
uint64(block.chainid),
_message
)
);
}
function executeMessage(MsgDataTypes.Route memory _route, bytes calldata _message)
private
returns (IMessageReceiverApp.ExecutionStatus)
{
uint256 gasLeftBeforeExecution = gasleft();
bool ok;
bytes memory res;
if (_route.senderBytes.length == 0) {
(ok, res) = address(_route.receiver).call{value: msg.value}(
abi.encodeWithSelector(
bytes4(keccak256(bytes("executeMessage(address,uint64,bytes,address)"))),
_route.sender,
_route.srcChainId,
_message,
msg.sender
)
);
} else {
(ok, res) = address(_route.receiver).call{value: msg.value}(
abi.encodeWithSelector(
bytes4(keccak256(bytes("executeMessage(bytes,uint64,bytes,address)"))),
_route.senderBytes,
_route.srcChainId,
_message,
msg.sender
)
);
}
if (ok) {
return abi.decode((res), (IMessageReceiverApp.ExecutionStatus));
}
handleExecutionRevert(gasLeftBeforeExecution, res);
return IMessageReceiverApp.ExecutionStatus.Fail;
}
function handleExecutionRevert(uint256 _gasLeftBeforeExecution, bytes memory _returnData) private {
uint256 gasLeftAfterExecution = gasleft();
uint256 maxTargetGasLimit = block.gaslimit - preExecuteMessageGasUsage;
if (_gasLeftBeforeExecution < maxTargetGasLimit && gasLeftAfterExecution <= _gasLeftBeforeExecution / 64) {
// if this happens, the executor must have not provided sufficient gas limit,
// then the tx should revert instead of recording a non-retryable failure status
// https://github.com/wolflo/evm-opcodes/blob/main/gas.md#aa-f-gas-to-send-with-call-operations
assembly {
invalid()
}
}
string memory revertMsg = Utils.getRevertMsg(_returnData);
// revert the execution if the revert message has the ABORT prefix
checkAbortPrefix(revertMsg);
// otherwiase, emit revert message, return and mark the execution as failed (non-retryable)
emit CallReverted(revertMsg);
}
function checkAbortPrefix(string memory _revertMsg) private pure {
bytes memory prefixBytes = bytes(MsgDataTypes.ABORT_PREFIX);
bytes memory msgBytes = bytes(_revertMsg);
if (msgBytes.length >= prefixBytes.length) {
for (uint256 i = 0; i < prefixBytes.length; i++) {
if (msgBytes[i] != prefixBytes[i]) {
return; // prefix not match, return
}
}
revert(_revertMsg); // prefix match, revert
}
}
function getRouteInfo(MsgDataTypes.RouteInfo calldata _route) private pure returns (MsgDataTypes.Route memory) {
return MsgDataTypes.Route(_route.sender, "", _route.receiver, _route.srcChainId, _route.srcTxHash);
}
function getRouteInfo(MsgDataTypes.RouteInfo2 calldata _route) private pure returns (MsgDataTypes.Route memory) {
return MsgDataTypes.Route(address(0), _route.sender, _route.receiver, _route.srcChainId, _route.srcTxHash);
}
// ================= helper functions =====================
/**
* @notice combine bridge transfer and msg execution calls into a single tx
* @dev caller needs to get the required input params from SGN
* @param _tp params to call bridge transfer
* @param _mp params to execute message
*/
function transferAndExecuteMsg(
MsgDataTypes.BridgeTransferParams calldata _tp,
MsgDataTypes.MsgWithTransferExecutionParams calldata _mp
) external {
_bridgeTransfer(_mp.transfer.t, _tp);
executeMessageWithTransfer(_mp.message, _mp.transfer, _mp.sigs, _mp.signers, _mp.powers);
}
/**
* @notice combine bridge refund and msg execution calls into a single tx
* @dev caller needs to get the required input params from SGN
* @param _tp params to call bridge transfer for refund
* @param _mp params to execute message for refund
*/
function refundAndExecuteMsg(
MsgDataTypes.BridgeTransferParams calldata _tp,
MsgDataTypes.MsgWithTransferExecutionParams calldata _mp
) external {
_bridgeTransfer(_mp.transfer.t, _tp);
executeMessageWithTransferRefund(_mp.message, _mp.transfer, _mp.sigs, _mp.signers, _mp.powers);
}
function _bridgeTransfer(MsgDataTypes.TransferType t, MsgDataTypes.BridgeTransferParams calldata _params) private {
if (t == MsgDataTypes.TransferType.LqRelay) {
IBridge(liquidityBridge).relay(_params.request, _params.sigs, _params.signers, _params.powers);
} else if (t == MsgDataTypes.TransferType.LqWithdraw) {
IBridge(liquidityBridge).withdraw(_params.request, _params.sigs, _params.signers, _params.powers);
} else if (t == MsgDataTypes.TransferType.PegMint) {
IPeggedTokenBridge(pegBridge).mint(_params.request, _params.sigs, _params.signers, _params.powers);
} else if (t == MsgDataTypes.TransferType.PegV2Mint) {
IPeggedTokenBridgeV2(pegBridgeV2).mint(_params.request, _params.sigs, _params.signers, _params.powers);
} else if (t == MsgDataTypes.TransferType.PegWithdraw) {
IOriginalTokenVault(pegVault).withdraw(_params.request, _params.sigs, _params.signers, _params.powers);
} else if (t == MsgDataTypes.TransferType.PegV2Withdraw) {
IOriginalTokenVaultV2(pegVaultV2).withdraw(_params.request, _params.sigs, _params.signers, _params.powers);
}
}
// ================= contract config =================
function setLiquidityBridge(address _addr) public onlyOwner {
require(_addr != address(0), "invalid address");
liquidityBridge = _addr;
emit LiquidityBridgeUpdated(liquidityBridge);
}
function setPegBridge(address _addr) public onlyOwner {
require(_addr != address(0), "invalid address");
pegBridge = _addr;
emit PegBridgeUpdated(pegBridge);
}
function setPegVault(address _addr) public onlyOwner {
require(_addr != address(0), "invalid address");
pegVault = _addr;
emit PegVaultUpdated(pegVault);
}
function setPegBridgeV2(address _addr) public onlyOwner {
require(_addr != address(0), "invalid address");
pegBridgeV2 = _addr;
emit PegBridgeV2Updated(pegBridgeV2);
}
function setPegVaultV2(address _addr) public onlyOwner {
require(_addr != address(0), "invalid address");
pegVaultV2 = _addr;
emit PegVaultV2Updated(pegVaultV2);
}
function setPreExecuteMessageGasUsage(uint256 _usage) public onlyOwner {
preExecuteMessageGasUsage = _usage;
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.17;
import "../../safeguard/Ownable.sol";
import "../../interfaces/ISigsVerifier.sol";
contract MessageBusSender is Ownable {
ISigsVerifier public immutable sigsVerifier;
uint256 public feeBase;
uint256 public feePerByte;
mapping(address => uint256) public withdrawnFees;
event Message(address indexed sender, address receiver, uint256 dstChainId, bytes message, uint256 fee);
// message to non-evm chain with >20 bytes addr
event Message2(address indexed sender, bytes receiver, uint256 dstChainId, bytes message, uint256 fee);
event MessageWithTransfer(
address indexed sender,
address receiver,
uint256 dstChainId,
address bridge,
bytes32 srcTransferId,
bytes message,
uint256 fee
);
event FeeWithdrawn(address receiver, uint256 amount);
event FeeBaseUpdated(uint256 feeBase);
event FeePerByteUpdated(uint256 feePerByte);
constructor(ISigsVerifier _sigsVerifier) {
sigsVerifier = _sigsVerifier;
}
/**
* @notice Sends a message to a contract on another chain.
* Sender needs to make sure the uniqueness of the message Id, which is computed as
* hash(type.MessageOnly, sender, receiver, srcChainId, srcTxHash, dstChainId, message).
* If messages with the same Id are sent, only one of them will succeed at dst chain.
* 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 {
_sendMessage(_dstChainId, _message);
emit Message(msg.sender, _receiver, _dstChainId, _message, msg.value);
}
// Send message to non-evm chain with bytes for receiver address,
// otherwise same as above.
function sendMessage(
bytes calldata _receiver,
uint256 _dstChainId,
bytes calldata _message
) external payable {
_sendMessage(_dstChainId, _message);
emit Message2(msg.sender, _receiver, _dstChainId, _message, msg.value);
}
function _sendMessage(uint256 _dstChainId, bytes calldata _message) private {
require(_dstChainId != block.chainid, "Invalid chainId");
uint256 minFee = calcFee(_message);
require(msg.value >= minFee, "Insufficient fee");
}
/**
* @notice Sends a message associated with a transfer to a contract on another chain.
* If messages with the same srcTransferId are sent, only one of them will succeed.
* 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 {
require(_dstChainId != block.chainid, "Invalid chainId");
uint256 minFee = calcFee(_message);
require(msg.value >= minFee, "Insufficient fee");
// SGN needs to verify
// 1. msg.sender matches sender of the src transfer
// 2. dstChainId matches dstChainId of the src transfer
// 3. bridge is either liquidity bridge, peg src vault, or peg dst bridge
emit MessageWithTransfer(msg.sender, _receiver, _dstChainId, _srcBridge, _srcTransferId, _message, msg.value);
}
/**
* @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 {
bytes32 domain = keccak256(abi.encodePacked(block.chainid, address(this), "withdrawFee"));
sigsVerifier.verifySigs(abi.encodePacked(domain, _account, _cumulativeFee), _sigs, _signers, _powers);
uint256 amount = _cumulativeFee - withdrawnFees[_account];
require(amount > 0, "No new amount to withdraw");
withdrawnFees[_account] = _cumulativeFee;
(bool sent, ) = _account.call{value: amount, gas: 50000}("");
require(sent, "failed to withdraw fee");
emit FeeWithdrawn(_account, amount);
}
/**
* @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) public view returns (uint256) {
return feeBase + _message.length * feePerByte;
}
// -------------------- Admin --------------------
function setFeePerByte(uint256 _fee) external onlyOwner {
feePerByte = _fee;
emit FeePerByteUpdated(feePerByte);
}
function setFeeBase(uint256 _fee) external onlyOwner {
feeBase = _fee;
emit FeeBaseUpdated(feeBase);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*
* This adds a normal func that setOwner if _owner is address(0). So we can't allow
* renounceOwnership. So we can support Proxy based upgradable contract
*/
abstract contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_setOwner(msg.sender);
}
/**
* @dev Only to be called by inherit contracts, in their init func called by Proxy
* we require _owner == address(0), which is only possible when it's a delegateCall
* because constructor sets _owner in contract state.
*/
function initOwner() internal {
require(_owner == address(0), "owner already set");
_setOwner(msg.sender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == msg.sender, "Ownable: caller is not the owner");
_;
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
interface IMultichainEndpoint {
enum CallbackExecutionStatus {
Success,
Failed,
Retry
}
function executeMessageWithTransfer(
address _token,
uint256 _amount,
uint64 srcChainId,
bytes memory _message
) external payable returns (CallbackExecutionStatus);
function executeMessageWithTransferFallback(
address _token,
uint256 _amount,
bytes calldata _message
) external payable returns (CallbackExecutionStatus);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./celer/message/interfaces/IMessageBus.sol";
import "../../interfaces/ILuminexRouterV1.sol";
import '../../interfaces/IWROSE.sol';
import "../../libraries/FeesCollector.sol";
import "../chainvault/CrossChainVaultApp.sol";
import "./IMultichainEndpoint.sol";
contract MultichainEndpoint is IMultichainEndpoint, FeesCollector {
using SafeMath for uint;
using SafeERC20 for IERC20;
uint64 public constant SAPPHIRE_CHAINID = 0x5afe;
enum MultichainCommandType {
ProxyPass,
Receive
}
enum MessageStoreType {
MessageReceived,
MultichainMessageSent,
QueueUnwrapped
}
struct ConnectEndpointParams {
uint64 chainId;
address contractAddress;
}
struct EndpointFee {
uint256 settlementCost; // tx total cost on dst chain in dst chain currency
uint256 settlementCostInLocalCurrency; // tx total cost on dst chain in current chain's currency
}
struct SetEndpointFee {
uint64 chainId;
EndpointFee fee;
}
struct OFACEntry {
address addr;
bool isOfacListed;
}
address public nativeWrapper;
address public feeSetter;
mapping(uint64 => address) public connectedEndpoints;
mapping(address => uint64) public chainIdByEndpointAddress;
mapping(MessageStoreType => mapping(bytes32 => bool)) public settledMessages;
mapping(address => bool) public ofacBlocklist;
mapping(bytes32 => bool) internal _executedMessages;
mapping(bytes32 => bool) public _failedMessages;
mapping(bytes32 => address) private _messageRawHashToSender;
mapping(bytes32 => address) internal _dataHashToSender;
address public complianceManager;
mapping(uint64 => EndpointFee) public endpointsDestinationFees;
CrossChainVaultApp public immutable vaultApp;
address public immutable messageBus;
event MessageReceived(bytes32 indexed hashedEventKey, bool hasFailed);
event MultichainMessageSent(bytes32 indexed hashedEventKey);
event FeeUpdated(SetEndpointFee[] feeData);
event UpdateOFACList(OFACEntry[] list);
event ChangeComplianceManager(address newManager, address oldManager);
constructor(address payable _vaultApp) {
vaultApp = CrossChainVaultApp(_vaultApp);
messageBus = CrossChainVaultApp(_vaultApp).messageBus();
feeSetter = msg.sender;
complianceManager = msg.sender;
}
modifier onlyComplianceManager() {
require(msg.sender == complianceManager, "Not a compliance manager");
_;
}
function setComplianceManager(address newManager) public onlyComplianceManager {
emit ChangeComplianceManager(newManager, complianceManager);
complianceManager = newManager;
}
function setNativeWrapper(address _wrapper) public onlyOwner {
nativeWrapper = _wrapper;
}
function updateOFACList(OFACEntry[] memory list) public onlyComplianceManager {
require(block.chainid != SAPPHIRE_CHAINID);
emit UpdateOFACList(list);
for (uint i = 0; i < list.length; i++) {
ofacBlocklist[list[i].addr] = list[i].isOfacListed;
}
}
function setConnectedEndpoints(ConnectEndpointParams[] memory _endpoints) public onlyOwner {
for (uint i = 0; i < _endpoints.length; i++) {
require(_endpoints[i].chainId != block.chainid, "Can't add an endpoint to the current chain");
connectedEndpoints[_endpoints[i].chainId] = _endpoints[i].contractAddress;
chainIdByEndpointAddress[_endpoints[i].contractAddress] = _endpoints[i].chainId;
}
}
function setFeeSetter(address _feeSetter) public onlyOwner {
feeSetter = _feeSetter;
}
function setFixedFee(SetEndpointFee[] calldata feeData) public {
require(msg.sender == feeSetter, "Not allowed");
for (uint i = 0; i < feeData.length; i++) {
endpointsDestinationFees[feeData[i].chainId] = feeData[i].fee;
}
emit FeeUpdated(feeData);
}
function _multichainProxyPass(address token, uint256 amount, bytes memory data, uint256 fees) private {
address sapphireEndpoint = connectedEndpoints[SAPPHIRE_CHAINID];
require(sapphireEndpoint != address(0), "Sapphire endpoint is not yet configured");
bytes memory celerData = abi.encode(
msg.sender,
fees,
data
);
bytes memory bridgeTemplate = abi.encode(uint8(0), address(0), uint256(0));
uint _feesByCeler = IMessageBus(messageBus).calcFee(abi.encode(bridgeTemplate, celerData));
require(fees >= _feesByCeler + endpointsDestinationFees[SAPPHIRE_CHAINID].settlementCostInLocalCurrency, "Value is too low");
celerData = abi.encode(
msg.sender,
fees - _feesByCeler,
data
);
_depositFees(fees - _feesByCeler);
IERC20(token).safeIncreaseAllowance(address(vaultApp), amount);
vaultApp.lockAndMint{value: _feesByCeler}(
connectedEndpoints[SAPPHIRE_CHAINID],
SAPPHIRE_CHAINID,
token,
amount,
celerData
);
_messageRawHashToSender[keccak256(celerData)] = msg.sender;
settledMessages[MessageStoreType.MultichainMessageSent][keccak256(data)] = true;
emit MultichainMessageSent(keccak256(data));
}
function proxyPass(address token, uint256 amount, bytes memory encodedParams) public virtual payable {
require(!ofacBlocklist[msg.sender], "OFAC prohibited");
uint256 feesValue = msg.value;
if (token == nativeWrapper) {
require(msg.value >= amount, "Insufficient native amount");
IWROSE(nativeWrapper).deposit{value: amount}();
feesValue -= amount;
} else {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
}
require(block.chainid != SAPPHIRE_CHAINID, "Can't proxy pass from Sapphire");
_multichainProxyPass(token, amount, encodedParams, feesValue);
}
function executeMessageWithTransfer(
address _token,
uint256 _amount,
uint64 srcChainId,
bytes memory _message
) external override payable returns (CallbackExecutionStatus) {
require(msg.sender == address(vaultApp), "Unauthorized vault app");
require(!_executedMessages[keccak256(_message)], "Callback already executed");
(address sender, uint256 fee, bytes memory data) = _preprocessPayloadData(_message);
(bytes memory header,) = abi.decode(data, (bytes, bytes));
(uint8 commandTypeRaw) = abi.decode(header, (uint8));
MultichainCommandType commandType = MultichainCommandType(commandTypeRaw);
_executedMessages[keccak256(_message)] = true;
_dataHashToSender[keccak256(data)] = sender;
if (commandType == MultichainCommandType.ProxyPass) {
return _handleProxyPass(
data,
_amount,
_token,
fee * endpointsDestinationFees[srcChainId].settlementCostInLocalCurrency / endpointsDestinationFees[srcChainId].settlementCost
);
} else if (commandType == MultichainCommandType.Receive) {
return _handleReceive(data, _token, _amount, false);
}
return CallbackExecutionStatus.Failed;
}
function executeMessageWithTransferFallback(
address _token,
uint256 _amount,
bytes calldata _message
) external payable virtual override returns (CallbackExecutionStatus) {
require(msg.sender == address(vaultApp), "Unauthorized vault app");
require(!_executedMessages[keccak256(_message)], "Callback already executed");
(address sender,,) = _preprocessPayloadData(_message);
_transferTokensTo(sender, _token, _amount);
_executedMessages[keccak256(_message)] = true;
_failedMessages[keccak256(_message)] = true;
return CallbackExecutionStatus.Success;
}
function _preprocessPayloadData(bytes memory data) internal virtual view returns(address, uint256, bytes memory) {
return (address(0), 0, data);
}
function _beforeCommandExecution(MultichainCommandType cmd, bytes memory data, address token, uint256 amount) internal virtual {}
function _decodeProxyPassCommand(bytes memory _entry) internal pure returns (uint64, address, uint256, uint256, uint8) {
return abi.decode(_entry, (uint64, address, uint256, uint256, uint8));
}
function _encodeReceiveCommand(address dstAddress, bytes32 keyIndex) internal pure returns (bytes memory) {
return abi.encode(abi.encode(uint8(MultichainCommandType.Receive)), abi.encode(keyIndex, dstAddress));
}
receive() external payable {}
function _transferTokensTo(address _to, address _token, uint256 _amount) internal {
if (_token == nativeWrapper) {
IWROSE(_token).withdraw(_amount);
payable(_to).transfer(_amount);
return;
}
IERC20(_token).safeTransfer(_to, _amount);
}
function _handleReceive(bytes memory _data, address _token, uint256 _amount, bool failure) internal returns (CallbackExecutionStatus) {
(, bytes memory body) = abi.decode(_data, (bytes, bytes));
(bytes32 keyIndex, address dstAddress) = abi.decode(body, (bytes32, address));
emit MessageReceived(keyIndex, failure);
settledMessages[MessageStoreType.MessageReceived][keyIndex] = true;
_transferTokensTo(dstAddress, _token, _amount);
return CallbackExecutionStatus.Success;
}
function _handleProxyPass(bytes memory, uint256, address, uint256) internal virtual returns (CallbackExecutionStatus) {
return CallbackExecutionStatus.Success;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
interface ILuminexRouterV1 {
function factory() external view returns (address);
function WROSE() external view returns (address);
function precalculateAmounts(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) external view returns (uint amountA, uint amountB);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityROSE(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountROSEMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountROSE, uint liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB);
function removeLiquidityROSE(
address token,
uint liquidity,
uint amountTokenMin,
uint amountROSEMin,
address to,
uint deadline
) external returns (uint amountToken, uint amountROSE);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactROSEForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function swapTokensForExactROSE(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapExactTokensForROSE(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapROSEForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
function removeLiquidityROSESupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountROSEMin,
address to,
uint deadline
) external returns (uint amountROSE);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function swapExactROSEForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForROSESupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
interface IWROSE {
function deposit() external payable;
function transfer(address to, uint value) external returns (bool);
function withdraw(uint) external;
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../illuminex/op/celer/safeguard/Ownable.sol";
contract FeesCollector is Ownable {
uint256 private _feesCollected;
event FeesCollected(address indexed to, uint256 amount);
event FeesDeposited(uint256 amount);
function collectFees(address payable to, uint256 amount) public onlyOwner {
require(amount <= _feesCollected, "Insufficient fees collected");
_feesCollected -= amount;
to.transfer(amount);
emit FeesCollected(to, amount);
}
function _depositFees(uint256 amount) internal {
_feesCollected += amount;
emit FeesDeposited(amount);
}
}