Transaction Hash:
Block:
18881923 at Dec-28-2023 05:06:11 AM +UTC
Transaction Fee:
0.005045638968106227 ETH
$12.75
Gas Used:
172,191 Gas / 29.302570797 Gwei
Emitted Events:
161 |
MaticToken.Transfer( from=[Sender] 0xae3642a03a1e4bd7ab7d919d14c54ecf1bfdddd3, to=0x9A47f3289794E9bbc6a3C571f6D96Ad4E7bAED16, value=153077860000000000000 )
|
162 |
MaticToken.Approval( owner=[Sender] 0xae3642a03a1e4bd7ab7d919d14c54ecf1bfdddd3, spender=[Receiver] MetaBridge, value=0 )
|
163 |
MaticToken.Transfer( from=0x9A47f3289794E9bbc6a3C571f6D96Ad4E7bAED16, to=GnosisSafeProxy, value=1339431275000000000 )
|
164 |
0x9a47f3289794e9bbc6a3c571f6d96ad4e7baed16.0x6ded982279c8387ad8a63e73385031a3807c1862e633f06e09d11bcb6e282f60( 0x6ded982279c8387ad8a63e73385031a3807c1862e633f06e09d11bcb6e282f60, 0000000000000000000000007d1afa7b718fb893db30a3abc0cfc608aacfebb0, 000000000000000000000000e6b738da243e8fa2a0ed5915645789add5de5152, 00000000000000000000000000000000000000000000000012969da5e36c6e00 )
|
165 |
MaticToken.Transfer( from=0x9A47f3289794E9bbc6a3C571f6D96Ad4E7bAED16, to=HopFacetPacked, value=151738428725000000000 )
|
166 |
MaticToken.Approval( owner=0x9A47f3289794E9bbc6a3C571f6D96Ad4E7bAED16, spender=HopFacetPacked, value=115792089237316195423570985008687907853269984665640563464917258316113129639935 )
|
167 |
MaticToken.Transfer( from=HopFacetPacked, to=L1_ERC20_Bridge, value=151738428725000000000 )
|
168 |
MaticToken.Approval( owner=HopFacetPacked, spender=L1_ERC20_Bridge, value=115792089237316195423570985008687907853269984665640563211755751111565629639935 )
|
169 |
StateSender.StateSynced( id=2833765, contractAddress=0x8397259c...a11afa28a, data=0x00000000000000000000000029D591FF46194CE3B0B813CE7940569FA06BE7FA000000000000000000000000C01052C380C7D4D6DF84EB81A5B93A0C4F6762860000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000014000000000000000000000000022B1CBB8D98A01A3B71D034BB899775A76EB1CC2000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000C4CC29A306000000000000000000000000AE3642A03A1E4BD7AB7D919D14C54ECF1BFDDDD300000000000000000000000000000000000000000000000839CB30FB118DD2000000000000000000000000000000000000000000000000082AD2FB86A81A86190000000000000000000000000000000000000000000000000000000065963CC3000000000000000000000000D8781CA9163E9F132A4D8392332E64115688013A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 )
|
170 |
L1_ERC20_Bridge.TransferSentToL2( chainId=137, recipient=[Sender] 0xae3642a03a1e4bd7ab7d919d14c54ecf1bfdddd3, amount=151738428725000000000, amountOutMin=150659757840143123993, deadline=1704344771, relayer=0xd8781cA9...15688013a, relayerFee=0 )
|
171 |
HopFacetPacked.LiFiHopTransfer( _transactionId=System.Byte[] )
|
172 |
0x9a47f3289794e9bbc6a3c571f6d96ad4e7baed16.0x831bac9533a2034226daa21109dbd4f887674f0fe4877e1a8b35b3ffe1bdce76( 0x831bac9533a2034226daa21109dbd4f887674f0fe4877e1a8b35b3ffe1bdce76, 000000000000000000000000ae3642a03a1e4bd7ab7d919d14c54ecf1bfdddd3, 0000000000000000000000006ef81a18e1e432c289dc0d1a670b78e8bbf9aa35, 0000000000000000000000000000000000000000000000000000000000000089, 0000000000000000000000007d1afa7b718fb893db30a3abc0cfc608aacfebb0, 0000000000000000000000000000000000000000000000000000000000000000, 00000000000000000000000000000000000000000000000839cb30fb118dd200 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x22B1Cbb8...A76Eb1cc2 | (Hop Protocol: MATIC Bridge) | ||||
0x28e4F3a7...189A5bFbE | (Polygon (Matic): State Syncer) | ||||
0x7D1AfA7B...8AaCfeBB0 | |||||
0x88c6C46E...1dAA8134A
Miner
| (MEV Builder: 0x88c...34A) | 1.243321667897283826 Eth | 1.243338886997283826 Eth | 0.0000172191 | |
0xAE3642A0...f1BFdddd3 |
0.00782757955744784 Eth
Nonce: 3
|
0.002781940589341613 Eth
Nonce: 4
| 0.005045638968106227 |
Execution Trace
MetaBridge.bridge( adapterId=lifiAdapterV2, srcToken=0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0, amount=153077860000000000000, data=0x0000000000000000000000006EF81A18E1E432C289DC0D1A670B78E8BBF9AA350000000000000000000000006EF81A18E1E432C289DC0D1A670B78E8BBF9AA3500000000000000000000000000000000000000000000000000000000000000890000000000000000000000007D1AFA7B718FB893DB30A3ABC0CFC608AACFEBB0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000839CB30FB118DD200000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000012969DA5E36C6E00000000000000000000000000E6B738DA243E8FA2A0ED5915645789ADD5DE5152000000000000000000000000000000000000000000000000000000000000009033619A2DCDA881D5F5401535AE3642A03A1E4BD7AB7D919D14C54ECF1BFDDDD3000000897D1AFA7B718FB893DB30A3ABC0CFC608AACFEBB0000000000000000839CB30FB118DD20000000000000000082AD2FB86A81A8619D8781CA9163E9F132A4D8392332E64115688013A0000000000000000000000000000000022B1CBB8D98A01A3B71D034BB899775A76EB1CC200000000000000000000000000000000 )
-
MaticToken.transferFrom( from=0xAE3642A03a1e4bd7AB7D919d14C54ECf1BFdddd3, to=0x9A47f3289794E9bbc6a3C571f6D96Ad4E7bAED16, value=153077860000000000000 ) => ( True )
0x9a47f3289794e9bbc6a3c571f6d96ad4e7baed16.4cfee326( )
0x7ac070f096c6e20931c3dc54f927446be232618b.ab138240( )
-
MaticToken.transfer( to=0xe6b738DA243e8FA2a0eD5915645789aDD5dE5152, value=1339431275000000000 ) => ( True )
-
MaticToken.allowance( owner=0x9A47f3289794E9bbc6a3C571f6D96Ad4E7bAED16, spender=0x6eF81a18E1E432C289DC0d1a670B78E8bbF9AA35 ) => ( 115792089237316195423570985008687907853269984665640563465068996744838129639935 )
HopFacetPacked.startBridgeTokensViaHopL1ERC20Packed( )
-
MaticToken.transferFrom( from=0x9A47f3289794E9bbc6a3C571f6D96Ad4E7bAED16, to=0x6eF81a18E1E432C289DC0d1a670B78E8bbF9AA35, value=151738428725000000000 ) => ( True )
L1_ERC20_Bridge.sendToL2( chainId=137, recipient=0xAE3642A03a1e4bd7AB7D919d14C54ECf1BFdddd3, amount=151738428725000000000, amountOutMin=150659757840143123993, deadline=1704344771, relayer=0xd8781cA9163E9f132A4D8392332E64115688013a, relayerFee=0 )
-
MaticToken.transferFrom( from=0x6eF81a18E1E432C289DC0d1a670B78E8bbF9AA35, to=0x22B1Cbb8D98a01a3B71D034BB899775A76Eb1cc2, value=151738428725000000000 ) => ( True )
PolygonMessengerWrapper.sendCrossDomainMessage( _calldata=0xCC29A306000000000000000000000000AE3642A03A1E4BD7AB7D919D14C54ECF1BFDDDD300000000000000000000000000000000000000000000000839CB30FB118DD2000000000000000000000000000000000000000000000000082AD2FB86A81A86190000000000000000000000000000000000000000000000000000000065963CC3000000000000000000000000D8781CA9163E9F132A4D8392332E64115688013A0000000000000000000000000000000000000000000000000000000000000000 )
-
FxRoot.sendMessageToChild( _receiver=0xc01052c380c7D4D6df84eB81A5b93a0c4F676286, _data=0x00000000000000000000000022B1CBB8D98A01A3B71D034BB899775A76EB1CC2000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000C4CC29A306000000000000000000000000AE3642A03A1E4BD7AB7D919D14C54ECF1BFDDDD300000000000000000000000000000000000000000000000839CB30FB118DD2000000000000000000000000000000000000000000000000082AD2FB86A81A86190000000000000000000000000000000000000000000000000000000065963CC3000000000000000000000000D8781CA9163E9F132A4D8392332E64115688013A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 )
-
-
-
-
bridge[MetaBridge (ln:847)]
safeTransferFrom[MetaBridge (ln:857)]
bridge[MetaBridge (ln:865)]
encode[MetaBridge (ln:870)]
File 1 of 8: MetaBridge
File 2 of 8: MaticToken
File 3 of 8: GnosisSafeProxy
File 4 of 8: HopFacetPacked
File 5 of 8: L1_ERC20_Bridge
File 6 of 8: StateSender
File 7 of 8: PolygonMessengerWrapper
File 8 of 8: FxRoot
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) pragma solidity ^0.8.0; import "../utils/Context.sol"; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() { _transferOwnership(_msgSender()); } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { _checkOwner(); _; } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { require(owner() == _msgSender(), "Ownable: caller is not the owner"); } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby disabling any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol) pragma solidity ^0.8.0; import "../utils/Context.sol"; /** * @dev Contract module which allows children to implement an emergency stop * mechanism that can be triggered by an authorized account. * * This module is used through inheritance. It will make available the * modifiers `whenNotPaused` and `whenPaused`, which can be applied to * the functions of your contract. Note that they will not be pausable by * simply including this module, only once the modifiers are put in place. */ abstract contract Pausable is Context { /** * @dev Emitted when the pause is triggered by `account`. */ event Paused(address account); /** * @dev Emitted when the pause is lifted by `account`. */ event Unpaused(address account); bool private _paused; /** * @dev Initializes the contract in unpaused state. */ constructor() { _paused = false; } /** * @dev Modifier to make a function callable only when the contract is not paused. * * Requirements: * * - The contract must not be paused. */ modifier whenNotPaused() { _requireNotPaused(); _; } /** * @dev Modifier to make a function callable only when the contract is paused. * * Requirements: * * - The contract must be paused. */ modifier whenPaused() { _requirePaused(); _; } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { return _paused; } /** * @dev Throws if the contract is paused. */ function _requireNotPaused() internal view virtual { require(!paused(), "Pausable: paused"); } /** * @dev Throws if the contract is not paused. */ function _requirePaused() internal view virtual { require(paused(), "Pausable: not paused"); } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol) pragma solidity ^0.8.0; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor() { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == _ENTERED; } } // 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; } } pragma solidity ^0.8.0; import {IAdapter} from "./IAdapter.sol"; import {IBridge} from "./IBridge.sol"; import {ISpender} from "./ISpender.sol"; pragma solidity ^0.8.0; interface IAdapter { event Bridge( address recipient, address aggregator, uint256 destChain, address srcToken, address destToken, uint256 srcAmount ); event Fee(address srcToken, address feeWallet, uint256 fee); function bridge( address recipient, address aggregator, address spender, uint256 destChain, address srcToken, address destToken, uint256 srcAmount, bytes calldata data, uint256 fee, address payable feeWallet ) external payable; } pragma solidity ^0.8.0; interface IBridge { event AdapterSet(string adapterId, address addr); event AdapterRemoved(string adapterId); function setAdapter( string calldata adapterId, address adapterAddress ) external; function removeAdapter(string calldata adapterId) external; function bridge( string calldata adapterId, address tokenFrom, uint256 amount, bytes calldata data ) external payable; } pragma solidity ^0.8.0; interface ISpender { function bridge( address adapterAddress, bytes calldata data ) external payable; } pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IAdapter, IBridge, ISpender} from "contracts/interfaces/Exports.sol"; import {Constants} from "contracts/utils/Exports.sol"; import "./Spender.sol"; contract MetaBridge is IBridge, Ownable, Pausable, ReentrancyGuard { using SafeERC20 for IERC20; using Address for address; ISpender public immutable spender; // Mapping of adapterId to adapter mapping(string => address) public adapters; mapping(string => bool) public adapterRemoved; /** * @notice Constructor to create the MetaBridge * @param _owner The Owner of the MetaBridge set on deployment * @dev Deploys the Spender. Once the Spender is deployed it cannot be changed */ constructor(address _owner) { spender = new Spender(); _transferOwnership(_owner); } /** * @notice Sets the adapter for an aggregator. It can't be changed later. * @param adapterId Aggregator's identifier * @param adapterAddress Address of the contract that contains the logic for this aggregator */ function setAdapter( string calldata adapterId, address adapterAddress ) external override onlyOwner { require(adapterAddress.isContract(), "ADAPTER_IS_NOT_A_CONTRACT"); require(!adapterRemoved[adapterId], "ADAPTER_REMOVED"); require(adapters[adapterId] == address(0), "ADAPTER_EXISTS"); require(bytes(adapterId).length > 0, "INVALID_ADAPTED_ID"); adapters[adapterId] = adapterAddress; emit AdapterSet(adapterId, adapterAddress); } /** * @notice Removes the adapter for an existing aggregator. This can't be undone. * @param adapterId Adapter's identifier */ function removeAdapter( string calldata adapterId ) external override onlyOwner { require(adapters[adapterId] != address(0), "ADAPTER_DOES_NOT_EXIST"); delete adapters[adapterId]; adapterRemoved[adapterId] = true; emit AdapterRemoved(adapterId); } /** * @notice Performs a bridge * @param adapterId Identifier of the aggregator to be used for the bridge * @param srcToken Identifier of the source chain * @param amount Amount of tokens to be transferred from the destination chain * @param data Dynamic data which is passed in to the delegatecall made to the adapter * @dev pausable and nonreentrant */ function bridge( string calldata adapterId, address srcToken, uint256 amount, bytes calldata data ) external payable override whenNotPaused nonReentrant { address adapter = adapters[adapterId]; require(adapter != address(0), "ADAPTER_NOT_FOUND"); // Move ERC20 funds to the spender if (srcToken != Constants.NATIVE_TOKEN) { IERC20(srcToken).safeTransferFrom( msg.sender, address(spender), amount ); } else { require(msg.value == amount, "MSGVALUE_AMOUNT_MISMATCH"); } spender.bridge{value: msg.value}( adapter, abi.encodePacked( // bridge signature IAdapter.bridge.selector, abi.encode(msg.sender), data ) ); } /** * @notice Prevents the bridge function from being executed until the contract is unpaused. */ function pauseBridge() external onlyOwner { _pause(); } /** * @notice Unpauses the contract to make the bridge function callable. */ function unpauseBridge() external onlyOwner { _unpause(); } } pragma solidity ^0.8.0; import "@openzeppelin/contracts/utils/Address.sol"; import {IBridge, ISpender} from "contracts/interfaces/Exports.sol"; contract Spender is ISpender { using Address for address; IBridge public immutable metabridge; /** * @dev MetaBridge creates the Spender. Not intended to be called directly. */ constructor() public { metabridge = IBridge(msg.sender); } /** * @notice Performs a bridge * @param adapter Address of the adapter to be used for the bridge * @param data Dynamic data which is passed in to the delegatecall made to the adapter */ function bridge( address adapter, bytes calldata data ) external payable override { require(msg.sender == address(metabridge), "FORBIDDEN"); adapter.functionDelegateCall(data, "ADAPTER_DELEGATECALL_FAILED"); } } pragma solidity ^0.8.0; library Constants { address internal constant NATIVE_TOKEN = 0x0000000000000000000000000000000000000000; } pragma solidity ^0.8.0; import {Constants} from "./Constants.sol";
File 2 of 8: MaticToken
pragma solidity 0.5.2; // File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ interface IERC20 { function transfer(address to, uint256 value) external returns (bool); function approve(address spender, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); function totalSupply() external view returns (uint256); function balanceOf(address who) external view returns (uint256); function allowance(address owner, address spender) external view returns (uint256); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } // File: openzeppelin-solidity/contracts/math/SafeMath.sol /** * @title SafeMath * @dev Unsigned math operations with safety checks that revert on error */ library SafeMath { /** * @dev Multiplies two unsigned integers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // 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-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b); return c; } /** * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two unsigned integers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0); return a % b; } } // File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol /** * @title Standard ERC20 token * * @dev Implementation of the basic standard token. * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md * Originally based on code by FirstBlood: * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol * * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for * all accounts just by listening to said events. Note that this isn't required by the specification, and other * compliant implementations may not do it. */ contract ERC20 is IERC20 { using SafeMath for uint256; mapping (address => uint256) private _balances; mapping (address => mapping (address => uint256)) private _allowed; uint256 private _totalSupply; /** * @dev Total number of tokens in existence */ function totalSupply() public view returns (uint256) { return _totalSupply; } /** * @dev Gets the balance of the specified address. * @param owner The address to query the balance of. * @return An uint256 representing the amount owned by the passed address. */ function balanceOf(address owner) public view returns (uint256) { return _balances[owner]; } /** * @dev Function to check the amount of tokens that an owner allowed to a spender. * @param owner address The address which owns the funds. * @param spender address The address which will spend the funds. * @return A uint256 specifying the amount of tokens still available for the spender. */ function allowance(address owner, address spender) public view returns (uint256) { return _allowed[owner][spender]; } /** * @dev Transfer token for a specified address * @param to The address to transfer to. * @param value The amount to be transferred. */ function transfer(address to, uint256 value) public returns (bool) { _transfer(msg.sender, to, value); return true; } /** * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. * 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 * @param spender The address which will spend the funds. * @param value The amount of tokens to be spent. */ function approve(address spender, uint256 value) public returns (bool) { require(spender != address(0)); _allowed[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); return true; } /** * @dev Transfer tokens from one address to another. * Note that while this function emits an Approval event, this is not required as per the specification, * and other compliant implementations may not emit the event. * @param from address The address which you want to send tokens from * @param to address The address which you want to transfer to * @param value uint256 the amount of tokens to be transferred */ function transferFrom(address from, address to, uint256 value) public returns (bool) { _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); _transfer(from, to, value); emit Approval(from, msg.sender, _allowed[from][msg.sender]); return true; } /** * @dev Increase the amount of tokens that an owner allowed to a spender. * approve should be called when allowed_[_spender] == 0. To increment * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From MonolithDAO Token.sol * Emits an Approval event. * @param spender The address which will spend the funds. * @param addedValue The amount of tokens to increase the allowance by. */ function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { require(spender != address(0)); _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add(addedValue); emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); return true; } /** * @dev Decrease the amount of tokens that an owner allowed to a spender. * approve should be called when allowed_[_spender] == 0. To decrement * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From MonolithDAO Token.sol * Emits an Approval event. * @param spender The address which will spend the funds. * @param subtractedValue The amount of tokens to decrease the allowance by. */ function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { require(spender != address(0)); _allowed[msg.sender][spender] = _allowed[msg.sender][spender].sub(subtractedValue); emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); return true; } /** * @dev Transfer token for a specified addresses * @param from The address to transfer from. * @param to The address to transfer to. * @param value The amount to be transferred. */ function _transfer(address from, address to, uint256 value) internal { require(to != address(0)); _balances[from] = _balances[from].sub(value); _balances[to] = _balances[to].add(value); emit Transfer(from, to, value); } /** * @dev Internal function that mints an amount of the token and assigns it to * an account. This encapsulates the modification of balances such that the * proper events are emitted. * @param account The account that will receive the created tokens. * @param value The amount that will be created. */ function _mint(address account, uint256 value) internal { require(account != address(0)); _totalSupply = _totalSupply.add(value); _balances[account] = _balances[account].add(value); emit Transfer(address(0), account, value); } /** * @dev Internal function that burns an amount of the token of a given * account. * @param account The account whose tokens will be burnt. * @param value The amount that will be burnt. */ function _burn(address account, uint256 value) internal { require(account != address(0)); _totalSupply = _totalSupply.sub(value); _balances[account] = _balances[account].sub(value); emit Transfer(account, address(0), value); } /** * @dev Internal function that burns an amount of the token of a given * account, deducting from the sender's allowance for said account. Uses the * internal burn function. * Emits an Approval event (reflecting the reduced allowance). * @param account The account whose tokens will be burnt. * @param value The amount that will be burnt. */ function _burnFrom(address account, uint256 value) internal { _allowed[account][msg.sender] = _allowed[account][msg.sender].sub(value); _burn(account, value); emit Approval(account, msg.sender, _allowed[account][msg.sender]); } } // File: openzeppelin-solidity/contracts/access/Roles.sol /** * @title Roles * @dev Library for managing addresses assigned to a Role. */ library Roles { struct Role { mapping (address => bool) bearer; } /** * @dev give an account access to this role */ function add(Role storage role, address account) internal { require(account != address(0)); require(!has(role, account)); role.bearer[account] = true; } /** * @dev remove an account's access to this role */ function remove(Role storage role, address account) internal { require(account != address(0)); require(has(role, account)); role.bearer[account] = false; } /** * @dev check if an account has this role * @return bool */ function has(Role storage role, address account) internal view returns (bool) { require(account != address(0)); return role.bearer[account]; } } // File: openzeppelin-solidity/contracts/access/roles/PauserRole.sol contract PauserRole { using Roles for Roles.Role; event PauserAdded(address indexed account); event PauserRemoved(address indexed account); Roles.Role private _pausers; constructor () internal { _addPauser(msg.sender); } modifier onlyPauser() { require(isPauser(msg.sender)); _; } function isPauser(address account) public view returns (bool) { return _pausers.has(account); } function addPauser(address account) public onlyPauser { _addPauser(account); } function renouncePauser() public { _removePauser(msg.sender); } function _addPauser(address account) internal { _pausers.add(account); emit PauserAdded(account); } function _removePauser(address account) internal { _pausers.remove(account); emit PauserRemoved(account); } } // File: openzeppelin-solidity/contracts/lifecycle/Pausable.sol /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ contract Pausable is PauserRole { event Paused(address account); event Unpaused(address account); bool private _paused; constructor () internal { _paused = false; } /** * @return true if the contract is paused, false otherwise. */ function paused() public view returns (bool) { return _paused; } /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!_paused); _; } /** * @dev Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { require(_paused); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() public onlyPauser whenNotPaused { _paused = true; emit Paused(msg.sender); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() public onlyPauser whenPaused { _paused = false; emit Unpaused(msg.sender); } } // File: openzeppelin-solidity/contracts/token/ERC20/ERC20Pausable.sol /** * @title Pausable token * @dev ERC20 modified with pausable transfers. **/ contract ERC20Pausable is ERC20, Pausable { function transfer(address to, uint256 value) public whenNotPaused returns (bool) { return super.transfer(to, value); } function transferFrom(address from, address to, uint256 value) public whenNotPaused returns (bool) { return super.transferFrom(from, to, value); } function approve(address spender, uint256 value) public whenNotPaused returns (bool) { return super.approve(spender, value); } function increaseAllowance(address spender, uint addedValue) public whenNotPaused returns (bool success) { return super.increaseAllowance(spender, addedValue); } function decreaseAllowance(address spender, uint subtractedValue) public whenNotPaused returns (bool success) { return super.decreaseAllowance(spender, subtractedValue); } } // File: openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol /** * @title ERC20Detailed token * @dev The decimals are only for visualization purposes. * All the operations are done using the smallest and indivisible token unit, * just as on Ethereum all the operations are done in wei. */ contract ERC20Detailed is IERC20 { string private _name; string private _symbol; uint8 private _decimals; constructor (string memory name, string memory symbol, uint8 decimals) public { _name = name; _symbol = symbol; _decimals = decimals; } /** * @return the name of the token. */ function name() public view returns (string memory) { return _name; } /** * @return the symbol of the token. */ function symbol() public view returns (string memory) { return _symbol; } /** * @return the number of decimals of the token. */ function decimals() public view returns (uint8) { return _decimals; } } // File: contracts/MaticToken.sol contract MaticToken is ERC20Pausable, ERC20Detailed { constructor (string memory name, string memory symbol, uint8 decimals, uint256 totalSupply) public ERC20Detailed (name, symbol, decimals) { _mint(msg.sender, totalSupply); } }
File 3 of 8: GnosisSafeProxy
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title IProxy - Helper interface to access masterCopy of the Proxy on-chain /// @author Richard Meissner - <[email protected]> interface IProxy { function masterCopy() external view returns (address); } /// @title GnosisSafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract GnosisSafeProxy { // singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated. // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt` address internal singleton; /// @dev Constructor function sets address of singleton contract. /// @param _singleton Singleton address. constructor(address _singleton) { require(_singleton != address(0), "Invalid singleton address provided"); singleton = _singleton; } /// @dev Fallback function forwards all transactions and returns all received return data. fallback() external payable { // solhint-disable-next-line no-inline-assembly assembly { let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) { mstore(0, _singleton) return(0, 0x20) } calldatacopy(0, 0, calldatasize()) let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) if eq(success, 0) { revert(0, returndatasize()) } return(0, returndatasize()) } } } /// @title Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @author Stefan George - <[email protected]> contract GnosisSafeProxyFactory { event ProxyCreation(GnosisSafeProxy proxy, address singleton); /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @param singleton Address of singleton contract. /// @param data Payload for message call sent to new proxy contract. function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy) { proxy = new GnosisSafeProxy(singleton); if (data.length > 0) // solhint-disable-next-line no-inline-assembly assembly { if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) { revert(0, 0) } } emit ProxyCreation(proxy, singleton); } /// @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed. function proxyRuntimeCode() public pure returns (bytes memory) { return type(GnosisSafeProxy).runtimeCode; } /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address. function proxyCreationCode() public pure returns (bytes memory) { return type(GnosisSafeProxy).creationCode; } /// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer. /// This method is only meant as an utility to be called from other methods /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function deployProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) internal returns (GnosisSafeProxy proxy) { // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce)); bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton))); // solhint-disable-next-line no-inline-assembly assembly { proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt) } require(address(proxy) != address(0), "Create2 call failed"); } /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function createProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) public returns (GnosisSafeProxy proxy) { proxy = deployProxyWithNonce(_singleton, initializer, saltNonce); if (initializer.length > 0) // solhint-disable-next-line no-inline-assembly assembly { if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) { revert(0, 0) } } emit ProxyCreation(proxy, _singleton); } /// @dev Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. /// @param callback Callback that will be invoced after the new proxy contract has been successfully deployed and initialized. function createProxyWithCallback( address _singleton, bytes memory initializer, uint256 saltNonce, IProxyCreationCallback callback ) public returns (GnosisSafeProxy proxy) { uint256 saltNonceWithCallback = uint256(keccak256(abi.encodePacked(saltNonce, callback))); proxy = createProxyWithNonce(_singleton, initializer, saltNonceWithCallback); if (address(callback) != address(0)) callback.proxyCreated(proxy, _singleton, initializer, saltNonce); } /// @dev Allows to get the address for a new proxy contact created via `createProxyWithNonce` /// This method is only meant for address calculation purpose when you use an initializer that would revert, /// therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory. /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function calculateCreateProxyWithNonceAddress( address _singleton, bytes calldata initializer, uint256 saltNonce ) external returns (GnosisSafeProxy proxy) { proxy = deployProxyWithNonce(_singleton, initializer, saltNonce); revert(string(abi.encodePacked(proxy))); } } interface IProxyCreationCallback { function proxyCreated( GnosisSafeProxy proxy, address _singleton, bytes calldata initializer, uint256 saltNonce ) external; }
File 4 of 8: HopFacetPacked
// // SPDX-License-Identifier: MIT pragma solidity 0.8.17; import { IHopBridge, IL2AmmWrapper, ISwap } from "../Interfaces/IHopBridge.sol"; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { ERC20, SafeTransferLib } from "solmate/utils/SafeTransferLib.sol"; import { LibAsset, IERC20 } from "../Libraries/LibAsset.sol"; import { TransferrableOwnership } from "../Helpers/TransferrableOwnership.sol"; import { HopFacetOptimized } from "lifi/Facets/HopFacetOptimized.sol"; import { WETH } from "solmate/tokens/WETH.sol"; /// @title Hop Facet (Optimized for Rollups) /// @author LI.FI (https://li.fi) /// @notice Provides functionality for bridging through Hop /// @custom:version 1.0.6 contract HopFacetPacked is ILiFi, TransferrableOwnership { using SafeTransferLib for ERC20; /// Storage /// address public immutable nativeBridge; address public immutable nativeL2CanonicalToken; address public immutable nativeHToken; address public immutable nativeExchangeAddress; /// Errors /// error Invalid(); /// Events /// event LiFiHopTransfer(bytes8 _transactionId); /// Constructor /// /// @notice Initialize the contract. /// @param _owner The contract owner to approve tokens. /// @param _wrapper The address of Hop L2_AmmWrapper for native asset. constructor( address _owner, address _wrapper ) TransferrableOwnership(_owner) { bool wrapperIsSet = _wrapper != address(0); if (block.chainid == 1 && wrapperIsSet) { revert Invalid(); } nativeL2CanonicalToken = wrapperIsSet ? IL2AmmWrapper(_wrapper).l2CanonicalToken() : address(0); nativeHToken = wrapperIsSet ? IL2AmmWrapper(_wrapper).hToken() : address(0); nativeExchangeAddress = wrapperIsSet ? IL2AmmWrapper(_wrapper).exchangeAddress() : address(0); nativeBridge = wrapperIsSet ? IL2AmmWrapper(_wrapper).bridge() : address(0); } /// External Methods /// /// @dev Only meant to be called outside of the context of the diamond /// @notice Sets approval for the Hop Bridge to spend the specified token /// @param bridges The Hop Bridges to approve /// @param tokensToApprove The tokens to approve to approve to the Hop Bridges function setApprovalForHopBridges( address[] calldata bridges, address[] calldata tokensToApprove ) external onlyOwner { uint256 numBridges = bridges.length; for (uint256 i; i < numBridges; i++) { // Give Hop approval to bridge tokens LibAsset.maxApproveERC20( IERC20(tokensToApprove[i]), address(bridges[i]), type(uint256).max ); } } /// @notice Bridges Native tokens via Hop Protocol from L2 /// No params, all data will be extracted from manually encoded callData function startBridgeTokensViaHopL2NativePacked() external payable { // first 4 bytes are function signature // transactionId: bytes8(msg.data[4:12]), // receiver: address(bytes20(msg.data[12:32])), // destinationChainId: uint256(uint32(bytes4(msg.data[32:36]))), // bonderFee: uint256(uint128(bytes16(msg.data[36:52]))), // amountOutMin: uint256(uint128(bytes16(msg.data[52:68]))) // => total calldata length required: 68 uint256 destinationChainId = uint256(uint32(bytes4(msg.data[32:36]))); uint256 amountOutMin = uint256(uint128(bytes16(msg.data[52:68]))); bool toL1 = destinationChainId == 1; // Wrap ETH WETH(payable(nativeL2CanonicalToken)).deposit{ value: msg.value }(); // Exchange WETH for hToken uint256 swapAmount = ISwap(nativeExchangeAddress).swap( 0, 1, msg.value, amountOutMin, block.timestamp ); // Bridge assets // solhint-disable-next-line check-send-result IHopBridge(nativeBridge).send( destinationChainId, address(bytes20(msg.data[12:32])), // receiver swapAmount, uint256(uint128(bytes16(msg.data[36:52]))), // bonderFee toL1 ? 0 : amountOutMin, toL1 ? 0 : block.timestamp + 7 * 24 * 60 * 60 ); emit LiFiHopTransfer( bytes8(msg.data[4:12]) // transactionId ); } /// @notice Bridges Native tokens via Hop Protocol from L2 /// @param transactionId Custom transaction ID for tracking /// @param receiver Receiving wallet address /// @param destinationChainId Receiving chain /// @param bonderFee Fees payed to hop bonder /// @param amountOutMin Source swap minimal accepted amount /// @param destinationAmountOutMin Destination swap minimal accepted amount /// @param destinationDeadline Destination swap maximal time /// @param hopBridge Address of the Hop L2_AmmWrapper function startBridgeTokensViaHopL2NativeMin( bytes8 transactionId, address receiver, uint256 destinationChainId, uint256 bonderFee, uint256 amountOutMin, uint256 destinationAmountOutMin, uint256 destinationDeadline, address hopBridge ) external payable { // Bridge assets IHopBridge(hopBridge).swapAndSend{ value: msg.value }( destinationChainId, receiver, msg.value, bonderFee, amountOutMin, block.timestamp, destinationAmountOutMin, destinationDeadline ); emit LiFiHopTransfer(transactionId); } /// @notice Bridges Native tokens via Hop Protocol from L2 /// @param transactionId Custom transaction ID for tracking /// @param receiver Receiving wallet address /// @param destinationChainId Receiving chain /// @param bonderFee Fees payed to hop bonder /// @param amountOutMin Source swap minimal accepted amount function encode_startBridgeTokensViaHopL2NativePacked( bytes8 transactionId, address receiver, uint256 destinationChainId, uint256 bonderFee, uint256 amountOutMin ) external pure returns (bytes memory) { require( destinationChainId <= type(uint32).max, "destinationChainId value passed too big to fit in uint32" ); require( bonderFee <= type(uint128).max, "bonderFee value passed too big to fit in uint128" ); require( amountOutMin <= type(uint128).max, "amountOutMin value passed too big to fit in uint128" ); return bytes.concat( HopFacetPacked.startBridgeTokensViaHopL2NativePacked.selector, bytes8(transactionId), bytes20(receiver), bytes4(uint32(destinationChainId)), bytes16(uint128(bonderFee)), bytes16(uint128(amountOutMin)) ); } /// @notice Decodes calldata for startBridgeTokensViaHopL2NativePacked /// @param _data the calldata to decode function decode_startBridgeTokensViaHopL2NativePacked( bytes calldata _data ) external pure returns (BridgeData memory, HopFacetOptimized.HopData memory) { require( _data.length >= 68, "data passed in is not the correct length" ); BridgeData memory bridgeData; HopFacetOptimized.HopData memory hopData; bridgeData.transactionId = bytes32(bytes8(_data[4:12])); bridgeData.receiver = address(bytes20(_data[12:32])); bridgeData.destinationChainId = uint256(uint32(bytes4(_data[32:36]))); hopData.bonderFee = uint256(uint128(bytes16(_data[36:52]))); hopData.amountOutMin = uint256(uint128(bytes16(_data[52:68]))); return (bridgeData, hopData); } /// @notice Bridges ERC20 tokens via Hop Protocol from L2 /// No params, all data will be extracted from manually encoded callData function startBridgeTokensViaHopL2ERC20Packed() external { // first 4 bytes are function signature // transactionId: bytes8(msg.data[4:12]), // receiver: address(bytes20(msg.data[12:32])), // destinationChainId: uint256(uint32(bytes4(msg.data[32:36]))), // sendingAssetId: address(bytes20(msg.data[36:56])), // amount: uint256(uint128(bytes16(msg.data[56:72]))), // bonderFee: uint256(uint128(bytes16(msg.data[72:88]))), // amountOutMin: uint256(uint128(bytes16(msg.data[88:104]))), // destinationAmountOutMin: uint256(uint128(bytes16(msg.data[104:120]))), // destinationDeadline: uint256(uint32(bytes4(msg.data[120:124]))), // wrapper: address(bytes20(msg.data[124:144])) // => total calldata length required: 144 uint256 destinationChainId = uint256(uint32(bytes4(msg.data[32:36]))); uint256 amount = uint256(uint128(bytes16(msg.data[56:72]))); uint256 amountOutMin = uint256(uint128(bytes16(msg.data[88:104]))); bool toL1 = destinationChainId == 1; IL2AmmWrapper wrapper = IL2AmmWrapper( address(bytes20(msg.data[124:144])) ); // Deposit assets ERC20(address(bytes20(msg.data[36:56]))).safeTransferFrom( msg.sender, address(this), amount ); // Exchange sending asset to hToken uint256 swapAmount = ISwap(wrapper.exchangeAddress()).swap( 0, 1, amount, amountOutMin, block.timestamp ); // Bridge assets // solhint-disable-next-line check-send-result IHopBridge(wrapper.bridge()).send( destinationChainId, address(bytes20(msg.data[12:32])), swapAmount, uint256(uint128(bytes16(msg.data[72:88]))), toL1 ? 0 : uint256(uint128(bytes16(msg.data[104:120]))), toL1 ? 0 : uint256(uint32(bytes4(msg.data[120:124]))) ); emit LiFiHopTransfer(bytes8(msg.data[4:12])); } /// @notice Bridges ERC20 tokens via Hop Protocol from L2 /// @param transactionId Custom transaction ID for tracking /// @param receiver Receiving wallet address /// @param destinationChainId Receiving chain /// @param sendingAssetId Address of the source asset to bridge /// @param minAmount Amount of the source asset to bridge /// @param bonderFee Fees payed to hop bonder /// @param amountOutMin Source swap minimal accepted amount /// @param destinationAmountOutMin Destination swap minimal accepted amount /// @param destinationDeadline Destination swap maximal time /// @param hopBridge Address of the Hop L2_AmmWrapper function startBridgeTokensViaHopL2ERC20Min( bytes8 transactionId, address receiver, uint256 destinationChainId, address sendingAssetId, uint256 minAmount, uint256 bonderFee, uint256 amountOutMin, uint256 destinationAmountOutMin, uint256 destinationDeadline, address hopBridge ) external { // Deposit assets ERC20(sendingAssetId).safeTransferFrom( msg.sender, address(this), minAmount ); // Bridge assets IHopBridge(hopBridge).swapAndSend( destinationChainId, receiver, minAmount, bonderFee, amountOutMin, block.timestamp, destinationAmountOutMin, destinationDeadline ); emit LiFiHopTransfer(transactionId); } /// @notice Bridges ERC20 tokens via Hop Protocol from L2 /// @param transactionId Custom transaction ID for tracking /// @param receiver Receiving wallet address /// @param destinationChainId Receiving chain /// @param sendingAssetId Address of the source asset to bridge /// @param minAmount Amount of the source asset to bridge /// @param bonderFee Fees payed to hop bonder /// @param amountOutMin Source swap minimal accepted amount /// @param destinationAmountOutMin Destination swap minimal accepted amount /// @param destinationDeadline Destination swap maximal time /// @param wrapper Address of the Hop L2_AmmWrapper function encode_startBridgeTokensViaHopL2ERC20Packed( bytes32 transactionId, address receiver, uint256 destinationChainId, address sendingAssetId, uint256 minAmount, uint256 bonderFee, uint256 amountOutMin, uint256 destinationAmountOutMin, uint256 destinationDeadline, address wrapper ) external pure returns (bytes memory) { require( destinationChainId <= type(uint32).max, "destinationChainId value passed too big to fit in uint32" ); require( minAmount <= type(uint128).max, "amount value passed too big to fit in uint128" ); require( bonderFee <= type(uint128).max, "bonderFee value passed too big to fit in uint128" ); require( amountOutMin <= type(uint128).max, "amountOutMin value passed too big to fit in uint128" ); require( destinationAmountOutMin <= type(uint128).max, "destinationAmountOutMin value passed too big to fit in uint128" ); require( destinationDeadline <= type(uint32).max, "destinationDeadline value passed too big to fit in uint32" ); return bytes.concat( HopFacetPacked.startBridgeTokensViaHopL2ERC20Packed.selector, bytes8(transactionId), bytes20(receiver), bytes4(uint32(destinationChainId)), bytes20(sendingAssetId), bytes16(uint128(minAmount)), bytes16(uint128(bonderFee)), bytes16(uint128(amountOutMin)), bytes16(uint128(destinationAmountOutMin)), bytes4(uint32(destinationDeadline)), bytes20(wrapper) ); } /// @notice Decodes calldata for startBridgeTokensViaHopL2ERC20Packed /// @param _data the calldata to decode function decode_startBridgeTokensViaHopL2ERC20Packed( bytes calldata _data ) external pure returns (BridgeData memory, HopFacetOptimized.HopData memory) { require( _data.length >= 144, "data passed in is not the correct length" ); BridgeData memory bridgeData; HopFacetOptimized.HopData memory hopData; bridgeData.transactionId = bytes32(bytes8(_data[4:12])); bridgeData.receiver = address(bytes20(_data[12:32])); bridgeData.destinationChainId = uint256(uint32(bytes4(_data[32:36]))); bridgeData.sendingAssetId = address(bytes20(_data[36:56])); bridgeData.minAmount = uint256(uint128(bytes16(_data[56:72]))); hopData.bonderFee = uint256(uint128(bytes16(_data[72:88]))); hopData.amountOutMin = uint256(uint128(bytes16(_data[88:104]))); hopData.destinationAmountOutMin = uint256( uint128(bytes16(_data[104:120])) ); hopData.destinationDeadline = uint256(uint32(bytes4(_data[120:124]))); hopData.hopBridge = IHopBridge(address(bytes20(_data[124:144]))); return (bridgeData, hopData); } /// @notice Bridges Native tokens via Hop Protocol from L1 /// No params, all data will be extracted from manually encoded callData function startBridgeTokensViaHopL1NativePacked() external payable { // first 4 bytes are function signature // transactionId: bytes8(msg.data[4:12]), // receiver: address(bytes20(msg.data[12:32])), // destinationChainId: uint256(uint32(bytes4(msg.data[32:36]))), // destinationAmountOutMin: uint256(uint128(bytes16(msg.data[36:52]))), // relayer: address(bytes20(msg.data[52:72])), // relayerFee: uint256(uint128(bytes16(msg.data[72:88]))), // hopBridge: address(bytes20(msg.data[88:108])) // => total calldata length required: 108 // Bridge assets IHopBridge(address(bytes20(msg.data[88:108]))).sendToL2{ value: msg.value }( uint256(uint32(bytes4(msg.data[32:36]))), address(bytes20(msg.data[12:32])), msg.value, uint256(uint128(bytes16(msg.data[36:52]))), block.timestamp + 7 * 24 * 60 * 60, address(bytes20(msg.data[52:72])), uint256(uint128(bytes16(msg.data[72:88]))) ); emit LiFiHopTransfer(bytes8(msg.data[4:12])); } /// @notice Bridges Native tokens via Hop Protocol from L1 /// @param transactionId Custom transaction ID for tracking /// @param receiver Receiving wallet address /// @param destinationChainId Receiving chain /// @param destinationAmountOutMin Destination swap minimal accepted amount /// @param relayer needed for gas spikes /// @param relayerFee needed for gas spikes /// @param hopBridge Address of the Hop Bridge function startBridgeTokensViaHopL1NativeMin( bytes8 transactionId, address receiver, uint256 destinationChainId, uint256 destinationAmountOutMin, address relayer, uint256 relayerFee, address hopBridge ) external payable { // Bridge assets IHopBridge(hopBridge).sendToL2{ value: msg.value }( destinationChainId, receiver, msg.value, destinationAmountOutMin, block.timestamp + 7 * 24 * 60 * 60, relayer, relayerFee ); emit LiFiHopTransfer(transactionId); } /// @notice Bridges Native tokens via Hop Protocol from L1 /// @param transactionId Custom transaction ID for tracking /// @param receiver Receiving wallet address /// @param destinationChainId Receiving chain /// @param destinationAmountOutMin Destination swap minimal accepted amount /// @param relayer needed for gas spikes /// @param relayerFee needed for gas spikes /// @param hopBridge Address of the Hop Bridge function encode_startBridgeTokensViaHopL1NativePacked( bytes8 transactionId, address receiver, uint256 destinationChainId, uint256 destinationAmountOutMin, address relayer, uint256 relayerFee, address hopBridge ) external pure returns (bytes memory) { require( destinationChainId <= type(uint32).max, "destinationChainId value passed too big to fit in uint32" ); require( destinationAmountOutMin <= type(uint128).max, "destinationAmountOutMin value passed too big to fit in uint128" ); require( relayerFee <= type(uint128).max, "relayerFee value passed too big to fit in uint128" ); return bytes.concat( HopFacetPacked.startBridgeTokensViaHopL1NativePacked.selector, bytes8(transactionId), bytes20(receiver), bytes4(uint32(destinationChainId)), bytes16(uint128(destinationAmountOutMin)), bytes20(relayer), bytes16(uint128(relayerFee)), bytes20(hopBridge) ); } /// @notice Decodes calldata for startBridgeTokensViaHopL1NativePacked /// @param _data the calldata to decode function decode_startBridgeTokensViaHopL1NativePacked( bytes calldata _data ) external pure returns (BridgeData memory, HopFacetOptimized.HopData memory) { require( _data.length >= 108, "data passed in is not the correct length" ); BridgeData memory bridgeData; HopFacetOptimized.HopData memory hopData; bridgeData.transactionId = bytes32(bytes8(_data[4:12])); bridgeData.receiver = address(bytes20(_data[12:32])); bridgeData.destinationChainId = uint256(uint32(bytes4(_data[32:36]))); hopData.destinationAmountOutMin = uint256( uint128(bytes16(_data[36:52])) ); // relayer = address(bytes20(_data[52:72])); // relayerFee = uint256(uint128(bytes16(_data[72:88]))); hopData.hopBridge = IHopBridge(address(bytes20(_data[88:108]))); return (bridgeData, hopData); } /// @notice Bridges Native tokens via Hop Protocol from L1 /// No params, all data will be extracted from manually encoded callData function startBridgeTokensViaHopL1ERC20Packed() external payable { // first 4 bytes are function signature // transactionId: bytes8(msg.data[4:12]), // receiver: address(bytes20(msg.data[12:32])), // destinationChainId: uint256(uint32(bytes4(msg.data[32:36]))), // sendingAssetId: address(bytes20(msg.data[36:56])), // amount: uint256(uint128(bytes16(msg.data[56:72]))), // destinationAmountOutMin: uint256(uint128(bytes16(msg.data[72:88]))), // relayer: address(bytes20(msg.data[88:108])), // relayerFee: uint256(uint128(bytes16(msg.data[108:124]))), // hopBridge: address(bytes20(msg.data[124:144])) // => total calldata length required: 144 uint256 amount = uint256(uint128(bytes16(msg.data[56:72]))); // Deposit assets ERC20(address(bytes20(msg.data[36:56]))).safeTransferFrom( msg.sender, address(this), amount ); // Bridge assets IHopBridge(address(bytes20(msg.data[124:144]))).sendToL2( uint256(uint32(bytes4(msg.data[32:36]))), address(bytes20(msg.data[12:32])), amount, uint256(uint128(bytes16(msg.data[72:88]))), block.timestamp + 7 * 24 * 60 * 60, address(bytes20(msg.data[88:108])), uint256(uint128(bytes16(msg.data[108:124]))) ); emit LiFiHopTransfer(bytes8(msg.data[4:12])); } /// @notice Bridges ERC20 tokens via Hop Protocol from L1 /// @param transactionId Custom transaction ID for tracking /// @param receiver Receiving wallet address /// @param destinationChainId Receiving chain /// @param sendingAssetId Address of the source asset to bridge /// @param minAmount Amount of the source asset to bridge /// @param destinationAmountOutMin Destination swap minimal accepted amount /// @param relayer needed for gas spikes /// @param relayerFee needed for gas spikes /// @param hopBridge Address of the Hop Bridge function startBridgeTokensViaHopL1ERC20Min( bytes8 transactionId, address receiver, uint256 destinationChainId, address sendingAssetId, uint256 minAmount, uint256 destinationAmountOutMin, address relayer, uint256 relayerFee, address hopBridge ) external { // Deposit assets ERC20(sendingAssetId).safeTransferFrom( msg.sender, address(this), minAmount ); // Bridge assets IHopBridge(hopBridge).sendToL2( destinationChainId, receiver, minAmount, destinationAmountOutMin, block.timestamp + 7 * 24 * 60 * 60, relayer, relayerFee ); emit LiFiHopTransfer(transactionId); } /// @notice Bridges ERC20 tokens via Hop Protocol from L1 /// @param transactionId Custom transaction ID for tracking /// @param receiver Receiving wallet address /// @param destinationChainId Receiving chain /// @param sendingAssetId Address of the source asset to bridge /// @param minAmount Amount of the source asset to bridge /// @param destinationAmountOutMin Destination swap minimal accepted amount /// @param relayer needed for gas spikes /// @param relayerFee needed for gas spikes /// @param hopBridge Address of the Hop Bridge function encode_startBridgeTokensViaHopL1ERC20Packed( bytes8 transactionId, address receiver, uint256 destinationChainId, address sendingAssetId, uint256 minAmount, uint256 destinationAmountOutMin, address relayer, uint256 relayerFee, address hopBridge ) external pure returns (bytes memory) { require( destinationChainId <= type(uint32).max, "destinationChainId value passed too big to fit in uint32" ); require( minAmount <= type(uint128).max, "amount value passed too big to fit in uint128" ); require( destinationAmountOutMin <= type(uint128).max, "destinationAmountOutMin value passed too big to fit in uint128" ); require( relayerFee <= type(uint128).max, "relayerFee value passed too big to fit in uint128" ); return bytes.concat( HopFacetPacked.startBridgeTokensViaHopL1ERC20Packed.selector, bytes8(transactionId), bytes20(receiver), bytes4(uint32(destinationChainId)), bytes20(sendingAssetId), bytes16(uint128(minAmount)), bytes16(uint128(destinationAmountOutMin)), bytes20(relayer), bytes16(uint128(relayerFee)), bytes20(hopBridge) ); } /// @notice Decodes calldata for startBridgeTokensViaHopL1ERC20Packed /// @param _data the calldata to decode function decode_startBridgeTokensViaHopL1ERC20Packed( bytes calldata _data ) external pure returns (BridgeData memory, HopFacetOptimized.HopData memory) { require( _data.length >= 144, "data passed in is not the correct length" ); BridgeData memory bridgeData; HopFacetOptimized.HopData memory hopData; bridgeData.transactionId = bytes32(bytes8(_data[4:12])); bridgeData.receiver = address(bytes20(_data[12:32])); bridgeData.destinationChainId = uint256(uint32(bytes4(_data[32:36]))); bridgeData.sendingAssetId = address(bytes20(_data[36:56])); bridgeData.minAmount = uint256(uint128(bytes16(_data[56:72]))); hopData.destinationAmountOutMin = uint256( uint128(bytes16(_data[72:88])) ); // relayer = address(bytes20(_data[88:108])); // relayerFee = uint256(uint128(bytes16(_data[108:124]))); hopData.hopBridge = IHopBridge(address(bytes20(_data[124:144]))); return (bridgeData, hopData); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; interface IHopBridge { function sendToL2( uint256 chainId, address recipient, uint256 amount, uint256 amountOutMin, uint256 deadline, address relayer, uint256 relayerFee ) external payable; function swapAndSend( uint256 chainId, address recipient, uint256 amount, uint256 bonderFee, uint256 amountOutMin, uint256 deadline, uint256 destinationAmountOutMin, uint256 destinationDeadline ) external payable; function send( uint256 chainId, address recipient, uint256 amount, uint256 bonderFee, uint256 amountOutMin, uint256 deadline ) external; } interface IL2AmmWrapper { function bridge() external view returns (address); function l2CanonicalToken() external view returns (address); function hToken() external view returns (address); function exchangeAddress() external view returns (address); } interface ISwap { function swap( uint8 tokenIndexFrom, uint8 tokenIndexTo, uint256 dx, uint256 minDy, uint256 deadline ) external returns (uint256); } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; interface ILiFi { /// Structs /// struct BridgeData { bytes32 transactionId; string bridge; string integrator; address referrer; address sendingAssetId; address receiver; uint256 minAmount; uint256 destinationChainId; bool hasSourceSwaps; bool hasDestinationCall; } /// Events /// event LiFiTransferStarted(ILiFi.BridgeData bridgeData); event LiFiTransferCompleted( bytes32 indexed transactionId, address receivingAssetId, address receiver, uint256 amount, uint256 timestamp ); event LiFiTransferRecovered( bytes32 indexed transactionId, address receivingAssetId, address receiver, uint256 amount, uint256 timestamp ); event LiFiGenericSwapCompleted( bytes32 indexed transactionId, string integrator, string referrer, address receiver, address fromAssetId, address toAssetId, uint256 fromAmount, uint256 toAmount ); // Deprecated but kept here to include in ABI to parse historic events event LiFiSwappedGeneric( bytes32 indexed transactionId, string integrator, string referrer, address fromAssetId, address toAssetId, uint256 fromAmount, uint256 toAmount ); } // SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "../tokens/ERC20.sol"; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. /// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. library SafeTransferLib { /*////////////////////////////////////////////////////////////// ETH OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferETH(address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Transfer the ETH and store if it succeeded or not. success := call(gas(), to, amount, 0, 0, 0, 0) } require(success, "ETH_TRANSFER_FAILED"); } /*////////////////////////////////////////////////////////////// ERC20 OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferFrom( ERC20 token, address from, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument. mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) ) } require(success, "TRANSFER_FROM_FAILED"); } function safeTransfer( ERC20 token, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "TRANSFER_FAILED"); } function safeApprove( ERC20 token, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "APPROVE_FAILED"); } } // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; import { InsufficientBalance, NullAddrIsNotAnERC20Token, NullAddrIsNotAValidSpender, NoTransferToNullAddress, InvalidAmount, NativeAssetTransferFailed } from "../Errors/GenericErrors.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { LibSwap } from "./LibSwap.sol"; /// @title LibAsset /// @notice This library contains helpers for dealing with onchain transfers /// of assets, including accounting for the native asset `assetId` /// conventions and any noncompliant ERC20 transfers library LibAsset { uint256 private constant MAX_UINT = type(uint256).max; address internal constant NULL_ADDRESS = address(0); /// @dev All native assets use the empty address for their asset id /// by convention address internal constant NATIVE_ASSETID = NULL_ADDRESS; //address(0) /// @notice Gets the balance of the inheriting contract for the given asset /// @param assetId The asset identifier to get the balance of /// @return Balance held by contracts using this library function getOwnBalance(address assetId) internal view returns (uint256) { return isNativeAsset(assetId) ? address(this).balance : IERC20(assetId).balanceOf(address(this)); } /// @notice Transfers ether from the inheriting contract to a given /// recipient /// @param recipient Address to send ether to /// @param amount Amount to send to given recipient function transferNativeAsset( address payable recipient, uint256 amount ) private { if (recipient == NULL_ADDRESS) revert NoTransferToNullAddress(); if (amount > address(this).balance) revert InsufficientBalance(amount, address(this).balance); // solhint-disable-next-line avoid-low-level-calls (bool success, ) = recipient.call{ value: amount }(""); if (!success) revert NativeAssetTransferFailed(); } /// @notice If the current allowance is insufficient, the allowance for a given spender /// is set to MAX_UINT. /// @param assetId Token address to transfer /// @param spender Address to give spend approval to /// @param amount Amount to approve for spending function maxApproveERC20( IERC20 assetId, address spender, uint256 amount ) internal { if (isNativeAsset(address(assetId))) { return; } if (spender == NULL_ADDRESS) { revert NullAddrIsNotAValidSpender(); } if (assetId.allowance(address(this), spender) < amount) { SafeERC20.safeApprove(IERC20(assetId), spender, 0); SafeERC20.safeApprove(IERC20(assetId), spender, MAX_UINT); } } /// @notice Transfers tokens from the inheriting contract to a given /// recipient /// @param assetId Token address to transfer /// @param recipient Address to send token to /// @param amount Amount to send to given recipient function transferERC20( address assetId, address recipient, uint256 amount ) private { if (isNativeAsset(assetId)) { revert NullAddrIsNotAnERC20Token(); } if (recipient == NULL_ADDRESS) { revert NoTransferToNullAddress(); } uint256 assetBalance = IERC20(assetId).balanceOf(address(this)); if (amount > assetBalance) { revert InsufficientBalance(amount, assetBalance); } SafeERC20.safeTransfer(IERC20(assetId), recipient, amount); } /// @notice Transfers tokens from a sender to a given recipient /// @param assetId Token address to transfer /// @param from Address of sender/owner /// @param to Address of recipient/spender /// @param amount Amount to transfer from owner to spender function transferFromERC20( address assetId, address from, address to, uint256 amount ) internal { if (isNativeAsset(assetId)) { revert NullAddrIsNotAnERC20Token(); } if (to == NULL_ADDRESS) { revert NoTransferToNullAddress(); } IERC20 asset = IERC20(assetId); uint256 prevBalance = asset.balanceOf(to); SafeERC20.safeTransferFrom(asset, from, to, amount); if (asset.balanceOf(to) - prevBalance != amount) { revert InvalidAmount(); } } function depositAsset(address assetId, uint256 amount) internal { if (amount == 0) revert InvalidAmount(); if (isNativeAsset(assetId)) { if (msg.value < amount) revert InvalidAmount(); } else { uint256 balance = IERC20(assetId).balanceOf(msg.sender); if (balance < amount) revert InsufficientBalance(amount, balance); transferFromERC20(assetId, msg.sender, address(this), amount); } } function depositAssets(LibSwap.SwapData[] calldata swaps) internal { for (uint256 i = 0; i < swaps.length; ) { LibSwap.SwapData calldata swap = swaps[i]; if (swap.requiresDeposit) { depositAsset(swap.sendingAssetId, swap.fromAmount); } unchecked { i++; } } } /// @notice Determines whether the given assetId is the native asset /// @param assetId The asset identifier to evaluate /// @return Boolean indicating if the asset is the native asset function isNativeAsset(address assetId) internal pure returns (bool) { return assetId == NATIVE_ASSETID; } /// @notice Wrapper function to transfer a given asset (native or erc20) to /// some recipient. Should handle all non-compliant return value /// tokens as well by using the SafeERC20 contract by open zeppelin. /// @param assetId Asset id for transfer (address(0) for native asset, /// token address for erc20s) /// @param recipient Address to send asset to /// @param amount Amount to send to given recipient function transferAsset( address assetId, address payable recipient, uint256 amount ) internal { isNativeAsset(assetId) ? transferNativeAsset(recipient, amount) : transferERC20(assetId, recipient, amount); } /// @dev Checks whether the given address is a contract and contains code function isContract(address _contractAddr) internal view returns (bool) { uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(_contractAddr) } return size > 0; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; import { IERC173 } from "../Interfaces/IERC173.sol"; import { LibAsset } from "../Libraries/LibAsset.sol"; contract TransferrableOwnership is IERC173 { address public owner; address public pendingOwner; /// Errors /// error UnAuthorized(); error NoNullOwner(); error NewOwnerMustNotBeSelf(); error NoPendingOwnershipTransfer(); error NotPendingOwner(); /// Events /// event OwnershipTransferRequested( address indexed _from, address indexed _to ); constructor(address initialOwner) { owner = initialOwner; } modifier onlyOwner() { if (msg.sender != owner) revert UnAuthorized(); _; } /// @notice Initiates transfer of ownership to a new address /// @param _newOwner the address to transfer ownership to function transferOwnership(address _newOwner) external onlyOwner { if (_newOwner == LibAsset.NULL_ADDRESS) revert NoNullOwner(); if (_newOwner == msg.sender) revert NewOwnerMustNotBeSelf(); pendingOwner = _newOwner; emit OwnershipTransferRequested(msg.sender, pendingOwner); } /// @notice Cancel transfer of ownership function cancelOwnershipTransfer() external onlyOwner { if (pendingOwner == LibAsset.NULL_ADDRESS) revert NoPendingOwnershipTransfer(); pendingOwner = LibAsset.NULL_ADDRESS; } /// @notice Confirms transfer of ownership to the calling address (msg.sender) function confirmOwnershipTransfer() external { address _pendingOwner = pendingOwner; if (msg.sender != _pendingOwner) revert NotPendingOwner(); emit OwnershipTransferred(owner, _pendingOwner); owner = _pendingOwner; pendingOwner = LibAsset.NULL_ADDRESS; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { IHopBridge } from "../Interfaces/IHopBridge.sol"; import { LibAsset, IERC20 } from "../Libraries/LibAsset.sol"; import { SwapperV2, LibSwap } from "../Helpers/SwapperV2.sol"; import { LibDiamond } from "../Libraries/LibDiamond.sol"; /// @title Hop Facet (Optimized) /// @author LI.FI (https://li.fi) /// @notice Provides functionality for bridging through Hop /// @custom:version 2.0.0 contract HopFacetOptimized is ILiFi, SwapperV2 { /// Types /// struct HopData { uint256 bonderFee; uint256 amountOutMin; uint256 deadline; uint256 destinationAmountOutMin; uint256 destinationDeadline; IHopBridge hopBridge; address relayer; uint256 relayerFee; uint256 nativeFee; } /// External Methods /// /// @notice Sets approval for the Hop Bridge to spend the specified token /// @param bridges The Hop Bridges to approve /// @param tokensToApprove The tokens to approve to approve to the Hop Bridges function setApprovalForBridges( address[] calldata bridges, address[] calldata tokensToApprove ) external { LibDiamond.enforceIsContractOwner(); for (uint256 i; i < bridges.length; i++) { // Give Hop approval to bridge tokens LibAsset.maxApproveERC20( IERC20(tokensToApprove[i]), address(bridges[i]), type(uint256).max ); } } /// @notice Bridges ERC20 tokens via Hop Protocol from L1 /// @param _bridgeData the core information needed for bridging /// @param _hopData data specific to Hop Protocol function startBridgeTokensViaHopL1ERC20( ILiFi.BridgeData calldata _bridgeData, HopData calldata _hopData ) external payable { // Deposit assets LibAsset.transferFromERC20( _bridgeData.sendingAssetId, msg.sender, address(this), _bridgeData.minAmount ); // Bridge assets _hopData.hopBridge.sendToL2{ value: _hopData.nativeFee }( _bridgeData.destinationChainId, _bridgeData.receiver, _bridgeData.minAmount, _hopData.destinationAmountOutMin, _hopData.destinationDeadline, _hopData.relayer, _hopData.relayerFee ); emit LiFiTransferStarted(_bridgeData); } /// @notice Bridges Native tokens via Hop Protocol from L1 /// @param _bridgeData the core information needed for bridging /// @param _hopData data specific to Hop Protocol function startBridgeTokensViaHopL1Native( ILiFi.BridgeData calldata _bridgeData, HopData calldata _hopData ) external payable { // Bridge assets _hopData.hopBridge.sendToL2{ value: _bridgeData.minAmount + _hopData.nativeFee }( _bridgeData.destinationChainId, _bridgeData.receiver, _bridgeData.minAmount, _hopData.destinationAmountOutMin, _hopData.destinationDeadline, _hopData.relayer, _hopData.relayerFee ); emit LiFiTransferStarted(_bridgeData); } /// @notice Performs a swap before bridging ERC20 tokens via Hop Protocol from L1 /// @param _bridgeData the core information needed for bridging /// @param _swapData an array of swap related data for performing swaps before bridging /// @param _hopData data specific to Hop Protocol function swapAndStartBridgeTokensViaHopL1ERC20( ILiFi.BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, HopData calldata _hopData ) external payable { // Deposit and swap assets _bridgeData.minAmount = _depositAndSwap( _bridgeData.transactionId, _bridgeData.minAmount, _swapData, payable(msg.sender), _hopData.nativeFee ); // Bridge assets _hopData.hopBridge.sendToL2{ value: _hopData.nativeFee }( _bridgeData.destinationChainId, _bridgeData.receiver, _bridgeData.minAmount, _hopData.destinationAmountOutMin, _hopData.destinationDeadline, _hopData.relayer, _hopData.relayerFee ); emit LiFiTransferStarted(_bridgeData); } /// @notice Performs a swap before bridging Native tokens via Hop Protocol from L1 /// @param _bridgeData the core information needed for bridging /// @param _swapData an array of swap related data for performing swaps before bridging /// @param _hopData data specific to Hop Protocol function swapAndStartBridgeTokensViaHopL1Native( ILiFi.BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, HopData calldata _hopData ) external payable { // Deposit and swap assets _bridgeData.minAmount = _depositAndSwap( _bridgeData.transactionId, _bridgeData.minAmount, _swapData, payable(msg.sender), _hopData.nativeFee ); // Bridge assets _hopData.hopBridge.sendToL2{ value: _bridgeData.minAmount + _hopData.nativeFee }( _bridgeData.destinationChainId, _bridgeData.receiver, _bridgeData.minAmount, _hopData.destinationAmountOutMin, _hopData.destinationDeadline, _hopData.relayer, _hopData.relayerFee ); emit LiFiTransferStarted(_bridgeData); } /// @notice Bridges ERC20 tokens via Hop Protocol from L2 /// @param _bridgeData the core information needed for bridging /// @param _hopData data specific to Hop Protocol function startBridgeTokensViaHopL2ERC20( ILiFi.BridgeData calldata _bridgeData, HopData calldata _hopData ) external { // Deposit assets LibAsset.transferFromERC20( _bridgeData.sendingAssetId, msg.sender, address(this), _bridgeData.minAmount ); // Bridge assets _hopData.hopBridge.swapAndSend( _bridgeData.destinationChainId, _bridgeData.receiver, _bridgeData.minAmount, _hopData.bonderFee, _hopData.amountOutMin, _hopData.deadline, _hopData.destinationAmountOutMin, _hopData.destinationDeadline ); emit LiFiTransferStarted(_bridgeData); } /// @notice Bridges Native tokens via Hop Protocol from L2 /// @param _bridgeData the core information needed for bridging /// @param _hopData data specific to Hop Protocol function startBridgeTokensViaHopL2Native( ILiFi.BridgeData calldata _bridgeData, HopData calldata _hopData ) external payable { // Bridge assets _hopData.hopBridge.swapAndSend{ value: _bridgeData.minAmount }( _bridgeData.destinationChainId, _bridgeData.receiver, _bridgeData.minAmount, _hopData.bonderFee, _hopData.amountOutMin, _hopData.deadline, _hopData.destinationAmountOutMin, _hopData.destinationDeadline ); emit LiFiTransferStarted(_bridgeData); } /// @notice Performs a swap before bridging ERC20 tokens via Hop Protocol from L2 /// @param _bridgeData the core information needed for bridging /// @param _swapData an array of swap related data for performing swaps before bridging /// @param _hopData data specific to Hop Protocol function swapAndStartBridgeTokensViaHopL2ERC20( ILiFi.BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, HopData calldata _hopData ) external payable { // Deposit and swap assets _bridgeData.minAmount = _depositAndSwap( _bridgeData.transactionId, _bridgeData.minAmount, _swapData, payable(msg.sender) ); // Bridge assets _hopData.hopBridge.swapAndSend( _bridgeData.destinationChainId, _bridgeData.receiver, _bridgeData.minAmount, _hopData.bonderFee, _hopData.amountOutMin, _hopData.deadline, _hopData.destinationAmountOutMin, _hopData.destinationDeadline ); emit LiFiTransferStarted(_bridgeData); } /// @notice Performs a swap before bridging Native tokens via Hop Protocol from L2 /// @param _bridgeData the core information needed for bridging /// @param _swapData an array of swap related data for performing swaps before bridging /// @param _hopData data specific to Hop Protocol function swapAndStartBridgeTokensViaHopL2Native( ILiFi.BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, HopData calldata _hopData ) external payable { // Deposit and swap assets _bridgeData.minAmount = _depositAndSwap( _bridgeData.transactionId, _bridgeData.minAmount, _swapData, payable(msg.sender) ); // Bridge assets _hopData.hopBridge.swapAndSend{ value: _bridgeData.minAmount }( _bridgeData.destinationChainId, _bridgeData.receiver, _bridgeData.minAmount, _hopData.bonderFee, _hopData.amountOutMin, _hopData.deadline, _hopData.destinationAmountOutMin, _hopData.destinationDeadline ); emit LiFiTransferStarted(_bridgeData); } } // SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "./ERC20.sol"; import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; /// @notice Minimalist and modern Wrapped Ether implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/WETH.sol) /// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol) contract WETH is ERC20("Wrapped Ether", "WETH", 18) { using SafeTransferLib for address; event Deposit(address indexed from, uint256 amount); event Withdrawal(address indexed to, uint256 amount); function deposit() public payable virtual { _mint(msg.sender, msg.value); emit Deposit(msg.sender, msg.value); } function withdraw(uint256 amount) public virtual { _burn(msg.sender, amount); emit Withdrawal(msg.sender, amount); msg.sender.safeTransferETH(amount); } receive() external payable virtual { deposit(); } } // SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); /*////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor( string memory _name, string memory _symbol, uint8 _decimals ) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom( address from, address to, uint256 amount ) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\\x19\\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; error AlreadyInitialized(); error CannotAuthoriseSelf(); error CannotBridgeToSameNetwork(); error ContractCallNotAllowed(); error CumulativeSlippageTooHigh(uint256 minAmount, uint256 receivedAmount); error ExternalCallFailed(); error InformationMismatch(); error InsufficientBalance(uint256 required, uint256 balance); error InvalidAmount(); error InvalidCallData(); error InvalidConfig(); error InvalidContract(); error InvalidDestinationChain(); error InvalidFallbackAddress(); error InvalidReceiver(); error InvalidSendingToken(); error NativeAssetNotSupported(); error NativeAssetTransferFailed(); error NoSwapDataProvided(); error NoSwapFromZeroBalance(); error NotAContract(); error NotInitialized(); error NoTransferToNullAddress(); error NullAddrIsNotAnERC20Token(); error NullAddrIsNotAValidSpender(); error OnlyContractOwner(); error RecoveryAddressCannotBeZero(); error ReentrancyError(); error TokenNotSupported(); error UnAuthorized(); error UnsupportedChainId(uint256 chainId); error WithdrawFailed(); error ZeroAmount(); // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (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. Compatible with tokens that require the approval to be set to * 0 before setting it to a non-zero value. */ 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) (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 pragma solidity 0.8.17; import { LibAsset } from "./LibAsset.sol"; import { LibUtil } from "./LibUtil.sol"; import { InvalidContract, NoSwapFromZeroBalance, InsufficientBalance } from "../Errors/GenericErrors.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; library LibSwap { struct SwapData { address callTo; address approveTo; address sendingAssetId; address receivingAssetId; uint256 fromAmount; bytes callData; bool requiresDeposit; } event AssetSwapped( bytes32 transactionId, address dex, address fromAssetId, address toAssetId, uint256 fromAmount, uint256 toAmount, uint256 timestamp ); function swap(bytes32 transactionId, SwapData calldata _swap) internal { if (!LibAsset.isContract(_swap.callTo)) revert InvalidContract(); uint256 fromAmount = _swap.fromAmount; if (fromAmount == 0) revert NoSwapFromZeroBalance(); uint256 nativeValue = LibAsset.isNativeAsset(_swap.sendingAssetId) ? _swap.fromAmount : 0; uint256 initialSendingAssetBalance = LibAsset.getOwnBalance( _swap.sendingAssetId ); uint256 initialReceivingAssetBalance = LibAsset.getOwnBalance( _swap.receivingAssetId ); if (nativeValue == 0) { LibAsset.maxApproveERC20( IERC20(_swap.sendingAssetId), _swap.approveTo, _swap.fromAmount ); } if (initialSendingAssetBalance < _swap.fromAmount) { revert InsufficientBalance( _swap.fromAmount, initialSendingAssetBalance ); } // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory res) = _swap.callTo.call{ value: nativeValue }(_swap.callData); if (!success) { string memory reason = LibUtil.getRevertMsg(res); revert(reason); } uint256 newBalance = LibAsset.getOwnBalance(_swap.receivingAssetId); emit AssetSwapped( transactionId, _swap.callTo, _swap.sendingAssetId, _swap.receivingAssetId, _swap.fromAmount, newBalance > initialReceivingAssetBalance ? newBalance - initialReceivingAssetBalance : newBalance, block.timestamp ); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; /// @title ERC-173 Contract Ownership Standard /// Note: the ERC-165 identifier for this interface is 0x7f5828d0 /* is ERC165 */ interface IERC173 { /// @dev This emits when ownership of a contract changes. event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /// @notice Get the address of the owner /// @return owner_ The address of the owner. function owner() external view returns (address owner_); /// @notice Set the address of the new owner of the contract /// @dev Set _newOwner to address(0) to renounce any ownership. /// @param _newOwner The address of the new owner of the contract function transferOwnership(address _newOwner) external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { LibSwap } from "../Libraries/LibSwap.sol"; import { LibAsset } from "../Libraries/LibAsset.sol"; import { LibAllowList } from "../Libraries/LibAllowList.sol"; import { ContractCallNotAllowed, NoSwapDataProvided, CumulativeSlippageTooHigh } from "../Errors/GenericErrors.sol"; /// @title Swapper /// @author LI.FI (https://li.fi) /// @notice Abstract contract to provide swap functionality contract SwapperV2 is ILiFi { /// Types /// /// @dev only used to get around "Stack Too Deep" errors struct ReserveData { bytes32 transactionId; address payable leftoverReceiver; uint256 nativeReserve; } /// Modifiers /// /// @dev Sends any leftover balances back to the user /// @notice Sends any leftover balances to the user /// @param _swaps Swap data array /// @param _leftoverReceiver Address to send leftover tokens to /// @param _initialBalances Array of initial token balances modifier noLeftovers( LibSwap.SwapData[] calldata _swaps, address payable _leftoverReceiver, uint256[] memory _initialBalances ) { uint256 numSwaps = _swaps.length; if (numSwaps != 1) { address finalAsset = _swaps[numSwaps - 1].receivingAssetId; uint256 curBalance; _; for (uint256 i = 0; i < numSwaps - 1; ) { address curAsset = _swaps[i].receivingAssetId; // Handle multi-to-one swaps if (curAsset != finalAsset) { curBalance = LibAsset.getOwnBalance(curAsset) - _initialBalances[i]; if (curBalance > 0) { LibAsset.transferAsset( curAsset, _leftoverReceiver, curBalance ); } } unchecked { ++i; } } } else { _; } } /// @dev Sends any leftover balances back to the user reserving native tokens /// @notice Sends any leftover balances to the user /// @param _swaps Swap data array /// @param _leftoverReceiver Address to send leftover tokens to /// @param _initialBalances Array of initial token balances modifier noLeftoversReserve( LibSwap.SwapData[] calldata _swaps, address payable _leftoverReceiver, uint256[] memory _initialBalances, uint256 _nativeReserve ) { uint256 numSwaps = _swaps.length; if (numSwaps != 1) { address finalAsset = _swaps[numSwaps - 1].receivingAssetId; uint256 curBalance; _; for (uint256 i = 0; i < numSwaps - 1; ) { address curAsset = _swaps[i].receivingAssetId; // Handle multi-to-one swaps if (curAsset != finalAsset) { curBalance = LibAsset.getOwnBalance(curAsset) - _initialBalances[i]; uint256 reserve = LibAsset.isNativeAsset(curAsset) ? _nativeReserve : 0; if (curBalance > 0) { LibAsset.transferAsset( curAsset, _leftoverReceiver, curBalance - reserve ); } } unchecked { ++i; } } } else { _; } } /// @dev Refunds any excess native asset sent to the contract after the main function /// @notice Refunds any excess native asset sent to the contract after the main function /// @param _refundReceiver Address to send refunds to modifier refundExcessNative(address payable _refundReceiver) { uint256 initialBalance = address(this).balance - msg.value; _; uint256 finalBalance = address(this).balance; if (finalBalance > initialBalance) { LibAsset.transferAsset( LibAsset.NATIVE_ASSETID, _refundReceiver, finalBalance - initialBalance ); } } /// Internal Methods /// /// @dev Deposits value, executes swaps, and performs minimum amount check /// @param _transactionId the transaction id associated with the operation /// @param _minAmount the minimum amount of the final asset to receive /// @param _swaps Array of data used to execute swaps /// @param _leftoverReceiver The address to send leftover funds to /// @return uint256 result of the swap function _depositAndSwap( bytes32 _transactionId, uint256 _minAmount, LibSwap.SwapData[] calldata _swaps, address payable _leftoverReceiver ) internal returns (uint256) { uint256 numSwaps = _swaps.length; if (numSwaps == 0) { revert NoSwapDataProvided(); } address finalTokenId = _swaps[numSwaps - 1].receivingAssetId; uint256 initialBalance = LibAsset.getOwnBalance(finalTokenId); if (LibAsset.isNativeAsset(finalTokenId)) { initialBalance -= msg.value; } uint256[] memory initialBalances = _fetchBalances(_swaps); LibAsset.depositAssets(_swaps); _executeSwaps( _transactionId, _swaps, _leftoverReceiver, initialBalances ); uint256 newBalance = LibAsset.getOwnBalance(finalTokenId) - initialBalance; if (newBalance < _minAmount) { revert CumulativeSlippageTooHigh(_minAmount, newBalance); } return newBalance; } /// @dev Deposits value, executes swaps, and performs minimum amount check and reserves native token for fees /// @param _transactionId the transaction id associated with the operation /// @param _minAmount the minimum amount of the final asset to receive /// @param _swaps Array of data used to execute swaps /// @param _leftoverReceiver The address to send leftover funds to /// @param _nativeReserve Amount of native token to prevent from being swept back to the caller function _depositAndSwap( bytes32 _transactionId, uint256 _minAmount, LibSwap.SwapData[] calldata _swaps, address payable _leftoverReceiver, uint256 _nativeReserve ) internal returns (uint256) { uint256 numSwaps = _swaps.length; if (numSwaps == 0) { revert NoSwapDataProvided(); } address finalTokenId = _swaps[numSwaps - 1].receivingAssetId; uint256 initialBalance = LibAsset.getOwnBalance(finalTokenId); if (LibAsset.isNativeAsset(finalTokenId)) { initialBalance -= msg.value; } uint256[] memory initialBalances = _fetchBalances(_swaps); LibAsset.depositAssets(_swaps); ReserveData memory rd = ReserveData( _transactionId, _leftoverReceiver, _nativeReserve ); _executeSwaps(rd, _swaps, initialBalances); uint256 newBalance = LibAsset.getOwnBalance(finalTokenId) - initialBalance; if (LibAsset.isNativeAsset(finalTokenId)) { newBalance -= _nativeReserve; } if (newBalance < _minAmount) { revert CumulativeSlippageTooHigh(_minAmount, newBalance); } return newBalance; } /// Private Methods /// /// @dev Executes swaps and checks that DEXs used are in the allowList /// @param _transactionId the transaction id associated with the operation /// @param _swaps Array of data used to execute swaps /// @param _leftoverReceiver Address to send leftover tokens to /// @param _initialBalances Array of initial balances function _executeSwaps( bytes32 _transactionId, LibSwap.SwapData[] calldata _swaps, address payable _leftoverReceiver, uint256[] memory _initialBalances ) internal noLeftovers(_swaps, _leftoverReceiver, _initialBalances) { uint256 numSwaps = _swaps.length; for (uint256 i = 0; i < numSwaps; ) { LibSwap.SwapData calldata currentSwap = _swaps[i]; if ( !((LibAsset.isNativeAsset(currentSwap.sendingAssetId) || LibAllowList.contractIsAllowed(currentSwap.approveTo)) && LibAllowList.contractIsAllowed(currentSwap.callTo) && LibAllowList.selectorIsAllowed( bytes4(currentSwap.callData[:4]) )) ) revert ContractCallNotAllowed(); LibSwap.swap(_transactionId, currentSwap); unchecked { ++i; } } } /// @dev Executes swaps and checks that DEXs used are in the allowList /// @param _reserveData Data passed used to reserve native tokens /// @param _swaps Array of data used to execute swaps function _executeSwaps( ReserveData memory _reserveData, LibSwap.SwapData[] calldata _swaps, uint256[] memory _initialBalances ) internal noLeftoversReserve( _swaps, _reserveData.leftoverReceiver, _initialBalances, _reserveData.nativeReserve ) { uint256 numSwaps = _swaps.length; for (uint256 i = 0; i < numSwaps; ) { LibSwap.SwapData calldata currentSwap = _swaps[i]; if ( !((LibAsset.isNativeAsset(currentSwap.sendingAssetId) || LibAllowList.contractIsAllowed(currentSwap.approveTo)) && LibAllowList.contractIsAllowed(currentSwap.callTo) && LibAllowList.selectorIsAllowed( bytes4(currentSwap.callData[:4]) )) ) revert ContractCallNotAllowed(); LibSwap.swap(_reserveData.transactionId, currentSwap); unchecked { ++i; } } } /// @dev Fetches balances of tokens to be swapped before swapping. /// @param _swaps Array of data used to execute swaps /// @return uint256[] Array of token balances. function _fetchBalances( LibSwap.SwapData[] calldata _swaps ) private view returns (uint256[] memory) { uint256 numSwaps = _swaps.length; uint256[] memory balances = new uint256[](numSwaps); address asset; for (uint256 i = 0; i < numSwaps; ) { asset = _swaps[i].receivingAssetId; balances[i] = LibAsset.getOwnBalance(asset); if (LibAsset.isNativeAsset(asset)) { balances[i] -= msg.value; } unchecked { ++i; } } return balances; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; import { IDiamondCut } from "../Interfaces/IDiamondCut.sol"; import { LibUtil } from "../Libraries/LibUtil.sol"; import { OnlyContractOwner } from "../Errors/GenericErrors.sol"; /// Implementation of EIP-2535 Diamond Standard /// https://eips.ethereum.org/EIPS/eip-2535 library LibDiamond { bytes32 internal constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); // Diamond specific errors error IncorrectFacetCutAction(); error NoSelectorsInFace(); error FunctionAlreadyExists(); error FacetAddressIsZero(); error FacetAddressIsNotZero(); error FacetContainsNoCode(); error FunctionDoesNotExist(); error FunctionIsImmutable(); error InitZeroButCalldataNotEmpty(); error CalldataEmptyButInitNotZero(); error InitReverted(); // ---------------- struct FacetAddressAndPosition { address facetAddress; uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array } struct FacetFunctionSelectors { bytes4[] functionSelectors; uint256 facetAddressPosition; // position of facetAddress in facetAddresses array } struct DiamondStorage { // maps function selector to the facet address and // the position of the selector in the facetFunctionSelectors.selectors array mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition; // maps facet addresses to function selectors mapping(address => FacetFunctionSelectors) facetFunctionSelectors; // facet addresses address[] facetAddresses; // Used to query if a contract implements an interface. // Used to implement ERC-165. mapping(bytes4 => bool) supportedInterfaces; // owner of the contract address contractOwner; } function diamondStorage() internal pure returns (DiamondStorage storage ds) { bytes32 position = DIAMOND_STORAGE_POSITION; // solhint-disable-next-line no-inline-assembly assembly { ds.slot := position } } event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); function setContractOwner(address _newOwner) internal { DiamondStorage storage ds = diamondStorage(); address previousOwner = ds.contractOwner; ds.contractOwner = _newOwner; emit OwnershipTransferred(previousOwner, _newOwner); } function contractOwner() internal view returns (address contractOwner_) { contractOwner_ = diamondStorage().contractOwner; } function enforceIsContractOwner() internal view { if (msg.sender != diamondStorage().contractOwner) revert OnlyContractOwner(); } event DiamondCut( IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata ); // Internal function version of diamondCut function diamondCut( IDiamondCut.FacetCut[] memory _diamondCut, address _init, bytes memory _calldata ) internal { for (uint256 facetIndex; facetIndex < _diamondCut.length; ) { IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action; if (action == IDiamondCut.FacetCutAction.Add) { addFunctions( _diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors ); } else if (action == IDiamondCut.FacetCutAction.Replace) { replaceFunctions( _diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors ); } else if (action == IDiamondCut.FacetCutAction.Remove) { removeFunctions( _diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors ); } else { revert IncorrectFacetCutAction(); } unchecked { ++facetIndex; } } emit DiamondCut(_diamondCut, _init, _calldata); initializeDiamondCut(_init, _calldata); } function addFunctions( address _facetAddress, bytes4[] memory _functionSelectors ) internal { if (_functionSelectors.length == 0) { revert NoSelectorsInFace(); } DiamondStorage storage ds = diamondStorage(); if (LibUtil.isZeroAddress(_facetAddress)) { revert FacetAddressIsZero(); } uint96 selectorPosition = uint96( ds.facetFunctionSelectors[_facetAddress].functionSelectors.length ); // add new facet address if it does not exist if (selectorPosition == 0) { addFacet(ds, _facetAddress); } for ( uint256 selectorIndex; selectorIndex < _functionSelectors.length; ) { bytes4 selector = _functionSelectors[selectorIndex]; address oldFacetAddress = ds .selectorToFacetAndPosition[selector] .facetAddress; if (!LibUtil.isZeroAddress(oldFacetAddress)) { revert FunctionAlreadyExists(); } addFunction(ds, selector, selectorPosition, _facetAddress); unchecked { ++selectorPosition; ++selectorIndex; } } } function replaceFunctions( address _facetAddress, bytes4[] memory _functionSelectors ) internal { if (_functionSelectors.length == 0) { revert NoSelectorsInFace(); } DiamondStorage storage ds = diamondStorage(); if (LibUtil.isZeroAddress(_facetAddress)) { revert FacetAddressIsZero(); } uint96 selectorPosition = uint96( ds.facetFunctionSelectors[_facetAddress].functionSelectors.length ); // add new facet address if it does not exist if (selectorPosition == 0) { addFacet(ds, _facetAddress); } for ( uint256 selectorIndex; selectorIndex < _functionSelectors.length; ) { bytes4 selector = _functionSelectors[selectorIndex]; address oldFacetAddress = ds .selectorToFacetAndPosition[selector] .facetAddress; if (oldFacetAddress == _facetAddress) { revert FunctionAlreadyExists(); } removeFunction(ds, oldFacetAddress, selector); addFunction(ds, selector, selectorPosition, _facetAddress); unchecked { ++selectorPosition; ++selectorIndex; } } } function removeFunctions( address _facetAddress, bytes4[] memory _functionSelectors ) internal { if (_functionSelectors.length == 0) { revert NoSelectorsInFace(); } DiamondStorage storage ds = diamondStorage(); // if function does not exist then do nothing and return if (!LibUtil.isZeroAddress(_facetAddress)) { revert FacetAddressIsNotZero(); } for ( uint256 selectorIndex; selectorIndex < _functionSelectors.length; ) { bytes4 selector = _functionSelectors[selectorIndex]; address oldFacetAddress = ds .selectorToFacetAndPosition[selector] .facetAddress; removeFunction(ds, oldFacetAddress, selector); unchecked { ++selectorIndex; } } } function addFacet( DiamondStorage storage ds, address _facetAddress ) internal { enforceHasContractCode(_facetAddress); ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds .facetAddresses .length; ds.facetAddresses.push(_facetAddress); } function addFunction( DiamondStorage storage ds, bytes4 _selector, uint96 _selectorPosition, address _facetAddress ) internal { ds .selectorToFacetAndPosition[_selector] .functionSelectorPosition = _selectorPosition; ds.facetFunctionSelectors[_facetAddress].functionSelectors.push( _selector ); ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress; } function removeFunction( DiamondStorage storage ds, address _facetAddress, bytes4 _selector ) internal { if (LibUtil.isZeroAddress(_facetAddress)) { revert FunctionDoesNotExist(); } // an immutable function is a function defined directly in a diamond if (_facetAddress == address(this)) { revert FunctionIsImmutable(); } // replace selector with last selector, then delete last selector uint256 selectorPosition = ds .selectorToFacetAndPosition[_selector] .functionSelectorPosition; uint256 lastSelectorPosition = ds .facetFunctionSelectors[_facetAddress] .functionSelectors .length - 1; // if not the same then replace _selector with lastSelector if (selectorPosition != lastSelectorPosition) { bytes4 lastSelector = ds .facetFunctionSelectors[_facetAddress] .functionSelectors[lastSelectorPosition]; ds.facetFunctionSelectors[_facetAddress].functionSelectors[ selectorPosition ] = lastSelector; ds .selectorToFacetAndPosition[lastSelector] .functionSelectorPosition = uint96(selectorPosition); } // delete the last selector ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop(); delete ds.selectorToFacetAndPosition[_selector]; // if no more selectors for facet address then delete the facet address if (lastSelectorPosition == 0) { // replace facet address with last facet address and delete last facet address uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1; uint256 facetAddressPosition = ds .facetFunctionSelectors[_facetAddress] .facetAddressPosition; if (facetAddressPosition != lastFacetAddressPosition) { address lastFacetAddress = ds.facetAddresses[ lastFacetAddressPosition ]; ds.facetAddresses[facetAddressPosition] = lastFacetAddress; ds .facetFunctionSelectors[lastFacetAddress] .facetAddressPosition = facetAddressPosition; } ds.facetAddresses.pop(); delete ds .facetFunctionSelectors[_facetAddress] .facetAddressPosition; } } function initializeDiamondCut( address _init, bytes memory _calldata ) internal { if (LibUtil.isZeroAddress(_init)) { if (_calldata.length != 0) { revert InitZeroButCalldataNotEmpty(); } } else { if (_calldata.length == 0) { revert CalldataEmptyButInitNotZero(); } if (_init != address(this)) { enforceHasContractCode(_init); } // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory error) = _init.delegatecall(_calldata); if (!success) { if (error.length > 0) { // bubble up the error revert(string(error)); } else { revert InitReverted(); } } } } function enforceHasContractCode(address _contract) internal view { uint256 contractSize; // solhint-disable-next-line no-inline-assembly assembly { contractSize := extcodesize(_contract) } if (contractSize == 0) { revert FacetContainsNoCode(); } } } // 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) (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 pragma solidity 0.8.17; import "./LibBytes.sol"; library LibUtil { using LibBytes for bytes; function getRevertMsg( bytes memory _res ) internal pure returns (string memory) { // If the _res length is less than 68, then the transaction failed silently (without a revert message) if (_res.length < 68) return "Transaction reverted silently"; bytes memory revertData = _res.slice(4, _res.length - 4); // Remove the selector which is the first 4 bytes return abi.decode(revertData, (string)); // All that remains is the revert string } /// @notice Determines whether the given address is the zero address /// @param addr The address to verify /// @return Boolean indicating if the address is the zero address function isZeroAddress(address addr) internal pure returns (bool) { return addr == address(0); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; import { InvalidContract } from "../Errors/GenericErrors.sol"; /// @title Lib Allow List /// @author LI.FI (https://li.fi) /// @notice Library for managing and accessing the conract address allow list library LibAllowList { /// Storage /// bytes32 internal constant NAMESPACE = keccak256("com.lifi.library.allow.list"); struct AllowListStorage { mapping(address => bool) allowlist; mapping(bytes4 => bool) selectorAllowList; address[] contracts; } /// @dev Adds a contract address to the allow list /// @param _contract the contract address to add function addAllowedContract(address _contract) internal { _checkAddress(_contract); AllowListStorage storage als = _getStorage(); if (als.allowlist[_contract]) return; als.allowlist[_contract] = true; als.contracts.push(_contract); } /// @dev Checks whether a contract address has been added to the allow list /// @param _contract the contract address to check function contractIsAllowed( address _contract ) internal view returns (bool) { return _getStorage().allowlist[_contract]; } /// @dev Remove a contract address from the allow list /// @param _contract the contract address to remove function removeAllowedContract(address _contract) internal { AllowListStorage storage als = _getStorage(); if (!als.allowlist[_contract]) { return; } als.allowlist[_contract] = false; uint256 length = als.contracts.length; // Find the contract in the list for (uint256 i = 0; i < length; i++) { if (als.contracts[i] == _contract) { // Move the last element into the place to delete als.contracts[i] = als.contracts[length - 1]; // Remove the last element als.contracts.pop(); break; } } } /// @dev Fetch contract addresses from the allow list function getAllowedContracts() internal view returns (address[] memory) { return _getStorage().contracts; } /// @dev Add a selector to the allow list /// @param _selector the selector to add function addAllowedSelector(bytes4 _selector) internal { _getStorage().selectorAllowList[_selector] = true; } /// @dev Removes a selector from the allow list /// @param _selector the selector to remove function removeAllowedSelector(bytes4 _selector) internal { _getStorage().selectorAllowList[_selector] = false; } /// @dev Returns if selector has been added to the allow list /// @param _selector the selector to check function selectorIsAllowed(bytes4 _selector) internal view returns (bool) { return _getStorage().selectorAllowList[_selector]; } /// @dev Fetch local storage struct function _getStorage() internal pure returns (AllowListStorage storage als) { bytes32 position = NAMESPACE; // solhint-disable-next-line no-inline-assembly assembly { als.slot := position } } /// @dev Contains business logic for validating a contract address. /// @param _contract address of the dex to check function _checkAddress(address _contract) private view { if (_contract == address(0)) revert InvalidContract(); if (_contract.code.length == 0) revert InvalidContract(); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; interface IDiamondCut { enum FacetCutAction { Add, Replace, Remove } // Add=0, Replace=1, Remove=2 struct FacetCut { address facetAddress; FacetCutAction action; bytes4[] functionSelectors; } /// @notice Add/replace/remove any number of functions and optionally execute /// a function with delegatecall /// @param _diamondCut Contains the facet addresses and function selectors /// @param _init The address of the contract or facet to execute _calldata /// @param _calldata A function call, including function selector and arguments /// _calldata is executed with delegatecall on _init function diamondCut( FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata ) external; event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; library LibBytes { // solhint-disable no-inline-assembly // LibBytes specific errors error SliceOverflow(); error SliceOutOfBounds(); error AddressOutOfBounds(); bytes16 private constant _SYMBOLS = "0123456789abcdef"; // ------------------------- function slice( bytes memory _bytes, uint256 _start, uint256 _length ) internal pure returns (bytes memory) { if (_length + 31 < _length) revert SliceOverflow(); if (_bytes.length < _start + _length) revert SliceOutOfBounds(); bytes memory tempBytes; assembly { switch iszero(_length) case 0 { // Get a location of some free memory and store it in tempBytes as // Solidity does for memory variables. tempBytes := mload(0x40) // The first word of the slice result is potentially a partial // word read from the original array. To read it, we calculate // the length of that partial word and start copying that many // bytes into the array. The first word we copy will start with // data we don't care about, but the last `lengthmod` bytes will // land at the beginning of the contents of the new array. When // we're done copying, we overwrite the full first word with // the actual length of the slice. let lengthmod := and(_length, 31) // The multiplication in the next line is necessary // because when slicing multiples of 32 bytes (lengthmod == 0) // the following copy loop was copying the origin's length // and then ending prematurely not copying everything it should. let mc := add( add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)) ) let end := add(mc, _length) for { // The multiplication in the next line has the same exact purpose // as the one above. let cc := add( add( add(_bytes, lengthmod), mul(0x20, iszero(lengthmod)) ), _start ) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { mstore(mc, mload(cc)) } mstore(tempBytes, _length) //update free-memory pointer //allocating the array padded to 32 bytes like the compiler does now mstore(0x40, and(add(mc, 31), not(31))) } //if we want a zero-length slice let's just return a zero-length array default { tempBytes := mload(0x40) //zero out the 32 bytes slice we are about to return //we need to do it because Solidity does not garbage collect mstore(tempBytes, 0) mstore(0x40, add(tempBytes, 0x20)) } } return tempBytes; } function toAddress( bytes memory _bytes, uint256 _start ) internal pure returns (address) { if (_bytes.length < _start + 20) { revert AddressOutOfBounds(); } address tempAddress; assembly { tempAddress := div( mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000 ) } return tempAddress; } /// Copied from OpenZeppelin's `Strings.sol` utility library. /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8335676b0e99944eef6a742e16dcd9ff6e68e609/contracts/utils/Strings.sol function toHexString( uint256 value, uint256 length ) internal pure returns (string memory) { bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = _SYMBOLS[value & 0xf]; value >>= 4; } require(value == 0, "Strings: hex length insufficient"); return string(buffer); } }
File 5 of 8: L1_ERC20_Bridge
// SPDX-License-Identifier: MIT pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import "./L1_Bridge.sol"; /** * @dev A L1_Bridge that uses an ERC20 as the canonical token */ contract L1_ERC20_Bridge is L1_Bridge { using SafeERC20 for IERC20; IERC20 public immutable l1CanonicalToken; constructor (IERC20 _l1CanonicalToken, address[] memory bonders, address _governance) public L1_Bridge(bonders, _governance) { l1CanonicalToken = _l1CanonicalToken; } /* ========== Override Functions ========== */ function _transferFromBridge(address recipient, uint256 amount) internal override { l1CanonicalToken.safeTransfer(recipient, amount); } function _transferToBridge(address from, uint256 amount) internal override { l1CanonicalToken.safeTransferFrom(from, address(this), amount); } } // SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, 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 `sender` to `recipient` 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 sender, address recipient, uint256 amount) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } // SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; import "./IERC20.sol"; import "../../math/SafeMath.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 SafeMath for uint256; using Address for address; function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' // solhint-disable-next-line max-line-length require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).add(value); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); if (returndata.length > 0) { // Return data is optional // solhint-disable-next-line max-line-length require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } } } // SPDX-License-Identifier: MIT pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "./Bridge.sol"; import "../interfaces/IMessengerWrapper.sol"; /** * @dev L1_Bridge is responsible for the bonding and challenging of TransferRoots. All TransferRoots * originate in the L1_Bridge through `bondTransferRoot` and are propagated up to destination L2s. */ abstract contract L1_Bridge is Bridge { struct TransferBond { address bonder; uint256 createdAt; uint256 totalAmount; uint256 challengeStartTime; address challenger; bool challengeResolved; } /* ========== State ========== */ mapping(uint256 => mapping(bytes32 => uint256)) public transferRootCommittedAt; mapping(bytes32 => TransferBond) public transferBonds; mapping(uint256 => mapping(address => uint256)) public timeSlotToAmountBonded; mapping(uint256 => uint256) public chainBalance; /* ========== Config State ========== */ address public governance; mapping(uint256 => IMessengerWrapper) public crossDomainMessengerWrappers; mapping(uint256 => bool) public isChainIdPaused; uint256 public challengePeriod = 1 days; uint256 public challengeResolutionPeriod = 10 days; uint256 public minTransferRootBondDelay = 15 minutes; uint256 public constant CHALLENGE_AMOUNT_DIVISOR = 10; uint256 public constant TIME_SLOT_SIZE = 4 hours; /* ========== Events ========== */ event TransferSentToL2( uint256 indexed chainId, address indexed recipient, uint256 amount, uint256 amountOutMin, uint256 deadline, address indexed relayer, uint256 relayerFee ); event TransferRootBonded ( bytes32 indexed root, uint256 amount ); event TransferRootConfirmed( uint256 indexed originChainId, uint256 indexed destinationChainId, bytes32 indexed rootHash, uint256 totalAmount ); event TransferBondChallenged( bytes32 indexed transferRootId, bytes32 indexed rootHash, uint256 originalAmount ); event ChallengeResolved( bytes32 indexed transferRootId, bytes32 indexed rootHash, uint256 originalAmount ); /* ========== Modifiers ========== */ modifier onlyL2Bridge(uint256 chainId) { IMessengerWrapper messengerWrapper = crossDomainMessengerWrappers[chainId]; messengerWrapper.verifySender(msg.sender, msg.data); _; } constructor (address[] memory bonders, address _governance) public Bridge(bonders) { governance = _governance; } /* ========== Send Functions ========== */ /** * @notice `amountOutMin` and `deadline` should be 0 when no swap is intended at the destination. * @notice `amount` is the total amount the user wants to send including the relayer fee * @dev Send tokens to a supported layer-2 to mint hToken and optionally swap the hToken in the * AMM at the destination. * @param chainId The chainId of the destination chain * @param recipient The address receiving funds at the destination * @param amount The amount being sent * @param amountOutMin The minimum amount received after attempting to swap in the destination * AMM market. 0 if no swap is intended. * @param deadline The deadline for swapping in the destination AMM market. 0 if no * swap is intended. * @param relayer The address of the relayer at the destination. * @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`. */ function sendToL2( uint256 chainId, address recipient, uint256 amount, uint256 amountOutMin, uint256 deadline, address relayer, uint256 relayerFee ) external payable { IMessengerWrapper messengerWrapper = crossDomainMessengerWrappers[chainId]; require(messengerWrapper != IMessengerWrapper(0), "L1_BRG: chainId not supported"); require(isChainIdPaused[chainId] == false, "L1_BRG: Sends to this chainId are paused"); require(amount > 0, "L1_BRG: Must transfer a non-zero amount"); require(amount >= relayerFee, "L1_BRG: Relayer fee cannot exceed amount"); _transferToBridge(msg.sender, amount); bytes memory message = abi.encodeWithSignature( "distribute(address,uint256,uint256,uint256,address,uint256)", recipient, amount, amountOutMin, deadline, relayer, relayerFee ); chainBalance[chainId] = chainBalance[chainId].add(amount); messengerWrapper.sendCrossDomainMessage(message); emit TransferSentToL2( chainId, recipient, amount, amountOutMin, deadline, relayer, relayerFee ); } /* ========== TransferRoot Functions ========== */ /** * @dev Setting a TransferRoot is a two step process. * @dev 1. The TransferRoot is bonded with `bondTransferRoot`. Withdrawals can now begin on L1 * @dev and recipient L2's * @dev 2. The TransferRoot is confirmed after `confirmTransferRoot` is called by the l2 bridge * @dev where the TransferRoot originated. */ /** * @dev Used by the Bonder to bond a TransferRoot and propagate it up to destination L2s * @param rootHash The Merkle root of the TransferRoot Merkle tree * @param destinationChainId The id of the destination chain * @param totalAmount The amount destined for the destination chain */ function bondTransferRoot( bytes32 rootHash, uint256 destinationChainId, uint256 totalAmount ) external onlyBonder requirePositiveBalance { bytes32 transferRootId = getTransferRootId(rootHash, totalAmount); require(transferRootCommittedAt[destinationChainId][transferRootId] == 0, "L1_BRG: TransferRoot has already been confirmed"); require(transferBonds[transferRootId].createdAt == 0, "L1_BRG: TransferRoot has already been bonded"); uint256 currentTimeSlot = getTimeSlot(block.timestamp); uint256 bondAmount = getBondForTransferAmount(totalAmount); timeSlotToAmountBonded[currentTimeSlot][msg.sender] = timeSlotToAmountBonded[currentTimeSlot][msg.sender].add(bondAmount); transferBonds[transferRootId] = TransferBond( msg.sender, block.timestamp, totalAmount, uint256(0), address(0), false ); _distributeTransferRoot(rootHash, destinationChainId, totalAmount); emit TransferRootBonded(rootHash, totalAmount); } /** * @dev Used by an L2 bridge to confirm a TransferRoot via cross-domain message. Once a TransferRoot * has been confirmed, any challenge against that TransferRoot can be resolved as unsuccessful. * @param originChainId The id of the origin chain * @param rootHash The Merkle root of the TransferRoot Merkle tree * @param destinationChainId The id of the destination chain * @param totalAmount The amount destined for each destination chain * @param rootCommittedAt The block timestamp when the TransferRoot was committed on its origin chain */ function confirmTransferRoot( uint256 originChainId, bytes32 rootHash, uint256 destinationChainId, uint256 totalAmount, uint256 rootCommittedAt ) external onlyL2Bridge(originChainId) { bytes32 transferRootId = getTransferRootId(rootHash, totalAmount); require(transferRootCommittedAt[destinationChainId][transferRootId] == 0, "L1_BRG: TransferRoot already confirmed"); require(rootCommittedAt > 0, "L1_BRG: rootCommittedAt must be greater than 0"); transferRootCommittedAt[destinationChainId][transferRootId] = rootCommittedAt; chainBalance[originChainId] = chainBalance[originChainId].sub(totalAmount, "L1_BRG: Amount exceeds chainBalance. This indicates a layer-2 failure."); // If the TransferRoot was never bonded, distribute the TransferRoot. TransferBond storage transferBond = transferBonds[transferRootId]; if (transferBond.createdAt == 0) { _distributeTransferRoot(rootHash, destinationChainId, totalAmount); } emit TransferRootConfirmed(originChainId, destinationChainId, rootHash, totalAmount); } function _distributeTransferRoot( bytes32 rootHash, uint256 chainId, uint256 totalAmount ) internal { // Set TransferRoot on recipient Bridge if (chainId == getChainId()) { // Set L1 TransferRoot _setTransferRoot(rootHash, totalAmount); } else { chainBalance[chainId] = chainBalance[chainId].add(totalAmount); IMessengerWrapper messengerWrapper = crossDomainMessengerWrappers[chainId]; require(messengerWrapper != IMessengerWrapper(0), "L1_BRG: chainId not supported"); // Set L2 TransferRoot bytes memory setTransferRootMessage = abi.encodeWithSignature( "setTransferRoot(bytes32,uint256)", rootHash, totalAmount ); messengerWrapper.sendCrossDomainMessage(setTransferRootMessage); } } /* ========== External TransferRoot Challenges ========== */ /** * @dev Challenge a TransferRoot believed to be fraudulent * @param rootHash The Merkle root of the TransferRoot Merkle tree * @param originalAmount The total amount bonded for this TransferRoot * @param destinationChainId The id of the destination chain */ function challengeTransferBond(bytes32 rootHash, uint256 originalAmount, uint256 destinationChainId) external payable { bytes32 transferRootId = getTransferRootId(rootHash, originalAmount); TransferBond storage transferBond = transferBonds[transferRootId]; require(transferRootCommittedAt[destinationChainId][transferRootId] == 0, "L1_BRG: TransferRoot has already been confirmed"); require(transferBond.createdAt != 0, "L1_BRG: TransferRoot has not been bonded"); uint256 challengePeriodEnd = transferBond.createdAt.add(challengePeriod); require(challengePeriodEnd >= block.timestamp, "L1_BRG: TransferRoot cannot be challenged after challenge period"); require(transferBond.challengeStartTime == 0, "L1_BRG: TransferRoot already challenged"); transferBond.challengeStartTime = block.timestamp; transferBond.challenger = msg.sender; // Move amount from timeSlotToAmountBonded to debit uint256 timeSlot = getTimeSlot(transferBond.createdAt); uint256 bondAmount = getBondForTransferAmount(originalAmount); address bonder = transferBond.bonder; timeSlotToAmountBonded[timeSlot][bonder] = timeSlotToAmountBonded[timeSlot][bonder].sub(bondAmount); _addDebit(transferBond.bonder, bondAmount); // Get stake for challenge uint256 challengeStakeAmount = getChallengeAmountForTransferAmount(originalAmount); _transferToBridge(msg.sender, challengeStakeAmount); emit TransferBondChallenged(transferRootId, rootHash, originalAmount); } /** * @dev Resolve a challenge after the `challengeResolutionPeriod` has passed * @param rootHash The Merkle root of the TransferRoot Merkle tree * @param originalAmount The total amount originally bonded for this TransferRoot * @param destinationChainId The id of the destination chain */ function resolveChallenge(bytes32 rootHash, uint256 originalAmount, uint256 destinationChainId) external { bytes32 transferRootId = getTransferRootId(rootHash, originalAmount); TransferBond storage transferBond = transferBonds[transferRootId]; require(transferBond.challengeStartTime != 0, "L1_BRG: TransferRoot has not been challenged"); require(block.timestamp > transferBond.challengeStartTime.add(challengeResolutionPeriod), "L1_BRG: Challenge period has not ended"); require(transferBond.challengeResolved == false, "L1_BRG: TransferRoot already resolved"); transferBond.challengeResolved = true; uint256 challengeStakeAmount = getChallengeAmountForTransferAmount(originalAmount); if (transferRootCommittedAt[destinationChainId][transferRootId] > 0) { // Invalid challenge if (transferBond.createdAt > transferRootCommittedAt[destinationChainId][transferRootId].add(minTransferRootBondDelay)) { // Credit the bonder back with the bond amount plus the challenger's stake _addCredit(transferBond.bonder, getBondForTransferAmount(originalAmount).add(challengeStakeAmount)); } else { // If the TransferRoot was bonded before it was committed, the challenger and Bonder // get their stake back. This discourages Bonders from tricking challengers into // challenging a valid TransferRoots that haven't yet been committed. It also ensures // that Bonders are not punished if a TransferRoot is bonded too soon in error. // Return the challenger's stake _addCredit(transferBond.challenger, challengeStakeAmount); // Credit the bonder back with the bond amount _addCredit(transferBond.bonder, getBondForTransferAmount(originalAmount)); } } else { // Valid challenge // Burn 25% of the challengers stake _transferFromBridge(address(0xdead), challengeStakeAmount.mul(1).div(4)); // Reward challenger with the remaining 75% of their stake plus 100% of the Bonder's stake _addCredit(transferBond.challenger, challengeStakeAmount.mul(7).div(4)); } emit ChallengeResolved(transferRootId, rootHash, originalAmount); } /* ========== Override Functions ========== */ function _additionalDebit(address bonder) internal view override returns (uint256) { uint256 currentTimeSlot = getTimeSlot(block.timestamp); uint256 bonded = 0; uint256 numTimeSlots = challengePeriod / TIME_SLOT_SIZE; for (uint256 i = 0; i < numTimeSlots; i++) { bonded = bonded.add(timeSlotToAmountBonded[currentTimeSlot - i][bonder]); } return bonded; } function _requireIsGovernance() internal override { require(governance == msg.sender, "L1_BRG: Caller is not the owner"); } /* ========== External Config Management Setters ========== */ function setGovernance(address _newGovernance) external onlyGovernance { require(_newGovernance != address(0), "L1_BRG: _newGovernance cannot be address(0)"); governance = _newGovernance; } function setCrossDomainMessengerWrapper(uint256 chainId, IMessengerWrapper _crossDomainMessengerWrapper) external onlyGovernance { crossDomainMessengerWrappers[chainId] = _crossDomainMessengerWrapper; } function setChainIdDepositsPaused(uint256 chainId, bool isPaused) external onlyGovernance { isChainIdPaused[chainId] = isPaused; } function setChallengePeriod(uint256 _challengePeriod) external onlyGovernance { require(_challengePeriod % TIME_SLOT_SIZE == 0, "L1_BRG: challengePeriod must be divisible by TIME_SLOT_SIZE"); challengePeriod = _challengePeriod; } function setChallengeResolutionPeriod(uint256 _challengeResolutionPeriod) external onlyGovernance { challengeResolutionPeriod = _challengeResolutionPeriod; } function setMinTransferRootBondDelay(uint256 _minTransferRootBondDelay) external onlyGovernance { minTransferRootBondDelay = _minTransferRootBondDelay; } /* ========== Public Getters ========== */ function getBondForTransferAmount(uint256 amount) public pure returns (uint256) { // Bond covers amount plus a bounty to pay a potential challenger return amount.add(getChallengeAmountForTransferAmount(amount)); } function getChallengeAmountForTransferAmount(uint256 amount) public pure returns (uint256) { // Bond covers amount plus a bounty to pay a potential challenger return amount.div(CHALLENGE_AMOUNT_DIVISOR); } function getTimeSlot(uint256 time) public pure returns (uint256) { return time / TIME_SLOT_SIZE; } } // SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ 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) { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } /** * @dev Returns the substraction of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { 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) { // 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) { 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) { 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) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @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) { require(b <= a, "SafeMath: subtraction overflow"); 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) { if (a == 0) return 0; uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @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. 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) internal pure returns (uint256) { require(b > 0, "SafeMath: division by zero"); 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) { require(b > 0, "SafeMath: modulo by zero"); 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) { 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. * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {tryDiv}. * * 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) { 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) { require(b > 0, errorMessage); return a % b; } } // SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.8.0; /** * @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 * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize, which returns 0 for contracts in // construction, since the code is only stored at the end of the // constructor execution. uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (bool success, ) = recipient.call{ value: amount }(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain`call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); require(isContract(target), "Address: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: value }(data); return _verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { require(isContract(target), "Address: static call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.staticcall(data); return _verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { require(isContract(target), "Address: delegate call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.delegatecall(data); return _verifyCallResult(success, returndata, errorMessage); } function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable-next-line no-inline-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // SPDX-License-Identifier: MIT pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "./Accounting.sol"; import "../libraries/Lib_MerkleTree.sol"; /** * @dev Bridge extends the accounting system and encapsulates the logic that is shared by both the * L1 and L2 Bridges. It allows to TransferRoots to be set by parent contracts and for those * TransferRoots to be withdrawn against. It also allows the bonder to bond and withdraw Transfers * directly through `bondWithdrawal` and then settle those bonds against their TransferRoot once it * has been set. */ abstract contract Bridge is Accounting { using Lib_MerkleTree for bytes32; struct TransferRoot { uint256 total; uint256 amountWithdrawn; uint256 createdAt; } /* ========== Events ========== */ event Withdrew( bytes32 indexed transferId, address indexed recipient, uint256 amount, bytes32 transferNonce ); event WithdrawalBonded( bytes32 indexed transferId, uint256 amount ); event WithdrawalBondSettled( address indexed bonder, bytes32 indexed transferId, bytes32 indexed rootHash ); event MultipleWithdrawalsSettled( address indexed bonder, bytes32 indexed rootHash, uint256 totalBondsSettled ); event TransferRootSet( bytes32 indexed rootHash, uint256 totalAmount ); /* ========== State ========== */ mapping(bytes32 => TransferRoot) private _transferRoots; mapping(bytes32 => bool) private _spentTransferIds; mapping(address => mapping(bytes32 => uint256)) private _bondedWithdrawalAmounts; uint256 constant RESCUE_DELAY = 8 weeks; constructor(address[] memory bonders) public Accounting(bonders) {} /* ========== Public Getters ========== */ /** * @dev Get the hash that represents an individual Transfer. * @param chainId The id of the destination chain * @param recipient The address receiving the Transfer * @param amount The amount being transferred including the `_bonderFee` * @param transferNonce Used to avoid transferId collisions * @param bonderFee The amount paid to the address that withdraws the Transfer * @param amountOutMin The minimum amount received after attempting to swap in the destination * AMM market. 0 if no swap is intended. * @param deadline The deadline for swapping in the destination AMM market. 0 if no * swap is intended. */ function getTransferId( uint256 chainId, address recipient, uint256 amount, bytes32 transferNonce, uint256 bonderFee, uint256 amountOutMin, uint256 deadline ) public pure returns (bytes32) { return keccak256(abi.encode( chainId, recipient, amount, transferNonce, bonderFee, amountOutMin, deadline )); } /** * @notice getChainId can be overridden by subclasses if needed for compatibility or testing purposes. * @dev Get the current chainId * @return chainId The current chainId */ function getChainId() public virtual view returns (uint256 chainId) { this; // Silence state mutability warning without generating any additional byte code assembly { chainId := chainid() } } /** * @dev Get the TransferRoot id for a given rootHash and totalAmount * @param rootHash The Merkle root of the TransferRoot * @param totalAmount The total of all Transfers in the TransferRoot * @return The calculated transferRootId */ function getTransferRootId(bytes32 rootHash, uint256 totalAmount) public pure returns (bytes32) { return keccak256(abi.encodePacked(rootHash, totalAmount)); } /** * @dev Get the TransferRoot for a given rootHash and totalAmount * @param rootHash The Merkle root of the TransferRoot * @param totalAmount The total of all Transfers in the TransferRoot * @return The TransferRoot with the calculated transferRootId */ function getTransferRoot(bytes32 rootHash, uint256 totalAmount) public view returns (TransferRoot memory) { return _transferRoots[getTransferRootId(rootHash, totalAmount)]; } /** * @dev Get the amount bonded for the withdrawal of a transfer * @param bonder The Bonder of the withdrawal * @param transferId The Transfer's unique identifier * @return The amount bonded for a Transfer withdrawal */ function getBondedWithdrawalAmount(address bonder, bytes32 transferId) external view returns (uint256) { return _bondedWithdrawalAmounts[bonder][transferId]; } /** * @dev Get the spent status of a transfer ID * @param transferId The transfer's unique identifier * @return True if the transferId has been spent */ function isTransferIdSpent(bytes32 transferId) external view returns (bool) { return _spentTransferIds[transferId]; } /* ========== User/Relayer External Functions ========== */ /** * @notice Can be called by anyone (recipient or relayer) * @dev Withdraw a Transfer from its destination bridge * @param recipient The address receiving the Transfer * @param amount The amount being transferred including the `_bonderFee` * @param transferNonce Used to avoid transferId collisions * @param bonderFee The amount paid to the address that withdraws the Transfer * @param amountOutMin The minimum amount received after attempting to swap in the destination * AMM market. 0 if no swap is intended. (only used to calculate `transferId` in this function) * @param deadline The deadline for swapping in the destination AMM market. 0 if no * swap is intended. (only used to calculate `transferId` in this function) * @param rootHash The Merkle root of the TransferRoot * @param transferRootTotalAmount The total amount being transferred in a TransferRoot * @param transferIdTreeIndex The index of the transferId in the Merkle tree * @param siblings The siblings of the transferId in the Merkle tree * @param totalLeaves The total number of leaves in the Merkle tree */ function withdraw( address recipient, uint256 amount, bytes32 transferNonce, uint256 bonderFee, uint256 amountOutMin, uint256 deadline, bytes32 rootHash, uint256 transferRootTotalAmount, uint256 transferIdTreeIndex, bytes32[] calldata siblings, uint256 totalLeaves ) external nonReentrant { bytes32 transferId = getTransferId( getChainId(), recipient, amount, transferNonce, bonderFee, amountOutMin, deadline ); require( rootHash.verify( transferId, transferIdTreeIndex, siblings, totalLeaves ) , "BRG: Invalid transfer proof"); bytes32 transferRootId = getTransferRootId(rootHash, transferRootTotalAmount); _addToAmountWithdrawn(transferRootId, amount); _fulfillWithdraw(transferId, recipient, amount, uint256(0)); emit Withdrew(transferId, recipient, amount, transferNonce); } /** * @dev Allows the bonder to bond individual withdrawals before their TransferRoot has been committed. * @param recipient The address receiving the Transfer * @param amount The amount being transferred including the `_bonderFee` * @param transferNonce Used to avoid transferId collisions * @param bonderFee The amount paid to the address that withdraws the Transfer */ function bondWithdrawal( address recipient, uint256 amount, bytes32 transferNonce, uint256 bonderFee ) external onlyBonder requirePositiveBalance nonReentrant { bytes32 transferId = getTransferId( getChainId(), recipient, amount, transferNonce, bonderFee, 0, 0 ); _bondWithdrawal(transferId, amount); _fulfillWithdraw(transferId, recipient, amount, bonderFee); } /** * @dev Refunds the Bonder's stake from a bonded withdrawal and counts that withdrawal against * its TransferRoot. * @param bonder The Bonder of the withdrawal * @param transferId The Transfer's unique identifier * @param rootHash The Merkle root of the TransferRoot * @param transferRootTotalAmount The total amount being transferred in a TransferRoot * @param transferIdTreeIndex The index of the transferId in the Merkle tree * @param siblings The siblings of the transferId in the Merkle tree * @param totalLeaves The total number of leaves in the Merkle tree */ function settleBondedWithdrawal( address bonder, bytes32 transferId, bytes32 rootHash, uint256 transferRootTotalAmount, uint256 transferIdTreeIndex, bytes32[] calldata siblings, uint256 totalLeaves ) external { require( rootHash.verify( transferId, transferIdTreeIndex, siblings, totalLeaves ) , "BRG: Invalid transfer proof"); bytes32 transferRootId = getTransferRootId(rootHash, transferRootTotalAmount); uint256 amount = _bondedWithdrawalAmounts[bonder][transferId]; require(amount > 0, "L2_BRG: transferId has no bond"); _bondedWithdrawalAmounts[bonder][transferId] = 0; _addToAmountWithdrawn(transferRootId, amount); _addCredit(bonder, amount); emit WithdrawalBondSettled(bonder, transferId, rootHash); } /** * @dev Refunds the Bonder for all withdrawals that they bonded in a TransferRoot. * @param bonder The address of the Bonder being refunded * @param transferIds All transferIds in the TransferRoot in order * @param totalAmount The totalAmount of the TransferRoot */ function settleBondedWithdrawals( address bonder, // transferIds _must_ be calldata or it will be mutated by Lib_MerkleTree.getMerkleRoot bytes32[] calldata transferIds, uint256 totalAmount ) external { bytes32 rootHash = Lib_MerkleTree.getMerkleRoot(transferIds); bytes32 transferRootId = getTransferRootId(rootHash, totalAmount); uint256 totalBondsSettled = 0; for(uint256 i = 0; i < transferIds.length; i++) { uint256 transferBondAmount = _bondedWithdrawalAmounts[bonder][transferIds[i]]; if (transferBondAmount > 0) { totalBondsSettled = totalBondsSettled.add(transferBondAmount); _bondedWithdrawalAmounts[bonder][transferIds[i]] = 0; } } _addToAmountWithdrawn(transferRootId, totalBondsSettled); _addCredit(bonder, totalBondsSettled); emit MultipleWithdrawalsSettled(bonder, rootHash, totalBondsSettled); } /* ========== External TransferRoot Rescue ========== */ /** * @dev Allows governance to withdraw the remaining amount from a TransferRoot after the rescue delay has passed. * @param rootHash the Merkle root of the TransferRoot * @param originalAmount The TransferRoot's recorded total * @param recipient The address receiving the remaining balance */ function rescueTransferRoot(bytes32 rootHash, uint256 originalAmount, address recipient) external onlyGovernance { bytes32 transferRootId = getTransferRootId(rootHash, originalAmount); TransferRoot memory transferRoot = getTransferRoot(rootHash, originalAmount); require(transferRoot.createdAt != 0, "BRG: TransferRoot not found"); assert(transferRoot.total == originalAmount); uint256 rescueDelayEnd = transferRoot.createdAt.add(RESCUE_DELAY); require(block.timestamp >= rescueDelayEnd, "BRG: TransferRoot cannot be rescued before the Rescue Delay"); uint256 remainingAmount = transferRoot.total.sub(transferRoot.amountWithdrawn); _addToAmountWithdrawn(transferRootId, remainingAmount); _transferFromBridge(recipient, remainingAmount); } /* ========== Internal Functions ========== */ function _markTransferSpent(bytes32 transferId) internal { require(!_spentTransferIds[transferId], "BRG: The transfer has already been withdrawn"); _spentTransferIds[transferId] = true; } function _addToAmountWithdrawn(bytes32 transferRootId, uint256 amount) internal { TransferRoot storage transferRoot = _transferRoots[transferRootId]; require(transferRoot.total > 0, "BRG: Transfer root not found"); uint256 newAmountWithdrawn = transferRoot.amountWithdrawn.add(amount); require(newAmountWithdrawn <= transferRoot.total, "BRG: Withdrawal exceeds TransferRoot total"); transferRoot.amountWithdrawn = newAmountWithdrawn; } function _setTransferRoot(bytes32 rootHash, uint256 totalAmount) internal { bytes32 transferRootId = getTransferRootId(rootHash, totalAmount); require(_transferRoots[transferRootId].total == 0, "BRG: Transfer root already set"); require(totalAmount > 0, "BRG: Cannot set TransferRoot totalAmount of 0"); _transferRoots[transferRootId] = TransferRoot(totalAmount, 0, block.timestamp); emit TransferRootSet(rootHash, totalAmount); } function _bondWithdrawal(bytes32 transferId, uint256 amount) internal { require(_bondedWithdrawalAmounts[msg.sender][transferId] == 0, "BRG: Withdrawal has already been bonded"); _addDebit(msg.sender, amount); _bondedWithdrawalAmounts[msg.sender][transferId] = amount; emit WithdrawalBonded(transferId, amount); } /* ========== Private Functions ========== */ /// @dev Completes the Transfer, distributes the Bonder fee and marks the Transfer as spent. function _fulfillWithdraw( bytes32 transferId, address recipient, uint256 amount, uint256 bonderFee ) private { _markTransferSpent(transferId); _transferFromBridge(recipient, amount.sub(bonderFee)); if (bonderFee > 0) { _transferFromBridge(msg.sender, bonderFee); } } } // SPDX-License-Identifier: MIT pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; interface IMessengerWrapper { function sendCrossDomainMessage(bytes memory _calldata) external; function verifySender(address l1BridgeCaller, bytes memory _data) external; } // SPDX-License-Identifier: MIT pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; /** * @dev Accounting is an abstract contract that encapsulates the most critical logic in the Hop contracts. * The accounting system works by using two balances that can only increase `_credit` and `_debit`. * A bonder's available balance is the total credit minus the total debit. The contract exposes * two external functions that allows a bonder to stake and unstake and exposes two internal * functions to its child contracts that allow the child contract to add to the credit * and debit balance. In addition, child contracts can override `_additionalDebit` to account * for any additional debit balance in an alternative way. Lastly, it exposes a modifier, * `requirePositiveBalance`, that can be used by child contracts to ensure the bonder does not * use more than its available stake. */ abstract contract Accounting is ReentrancyGuard { using SafeMath for uint256; mapping(address => bool) private _isBonder; mapping(address => uint256) private _credit; mapping(address => uint256) private _debit; event Stake ( address indexed account, uint256 amount ); event Unstake ( address indexed account, uint256 amount ); event BonderAdded ( address indexed newBonder ); event BonderRemoved ( address indexed previousBonder ); /* ========== Modifiers ========== */ modifier onlyBonder { require(_isBonder[msg.sender], "ACT: Caller is not bonder"); _; } modifier onlyGovernance { _requireIsGovernance(); _; } /// @dev Used by parent contract to ensure that the Bonder is solvent at the end of the transaction. modifier requirePositiveBalance { _; require(getCredit(msg.sender) >= getDebitAndAdditionalDebit(msg.sender), "ACT: Not enough available credit"); } /// @dev Sets the Bonder addresses constructor(address[] memory bonders) public { for (uint256 i = 0; i < bonders.length; i++) { require(_isBonder[bonders[i]] == false, "ACT: Cannot add duplicate bonder"); _isBonder[bonders[i]] = true; emit BonderAdded(bonders[i]); } } /* ========== Virtual functions ========== */ /** * @dev The following functions are overridden in L1_Bridge and L2_Bridge */ function _transferFromBridge(address recipient, uint256 amount) internal virtual; function _transferToBridge(address from, uint256 amount) internal virtual; function _requireIsGovernance() internal virtual; /** * @dev This function can be optionally overridden by a parent contract to track any additional * debit balance in an alternative way. */ function _additionalDebit(address /*bonder*/) internal view virtual returns (uint256) { this; // Silence state mutability warning without generating any additional byte code return 0; } /* ========== Public/external getters ========== */ /** * @dev Check if address is a Bonder * @param maybeBonder The address being checked * @return true if address is a Bonder */ function getIsBonder(address maybeBonder) public view returns (bool) { return _isBonder[maybeBonder]; } /** * @dev Get the Bonder's credit balance * @param bonder The owner of the credit balance being checked * @return The credit balance for the Bonder */ function getCredit(address bonder) public view returns (uint256) { return _credit[bonder]; } /** * @dev Gets the debit balance tracked by `_debit` and does not include `_additionalDebit()` * @param bonder The owner of the debit balance being checked * @return The debit amount for the Bonder */ function getRawDebit(address bonder) external view returns (uint256) { return _debit[bonder]; } /** * @dev Get the Bonder's total debit * @param bonder The owner of the debit balance being checked * @return The Bonder's total debit balance */ function getDebitAndAdditionalDebit(address bonder) public view returns (uint256) { return _debit[bonder].add(_additionalDebit(bonder)); } /* ========== Bonder external functions ========== */ /** * @dev Allows the Bonder to deposit tokens and increase its credit balance * @param bonder The address being staked on * @param amount The amount being staked */ function stake(address bonder, uint256 amount) external payable nonReentrant { require(_isBonder[bonder] == true, "ACT: Address is not bonder"); _transferToBridge(msg.sender, amount); _addCredit(bonder, amount); emit Stake(bonder, amount); } /** * @dev Allows the caller to withdraw any available balance and add to their debit balance * @param amount The amount being unstaked */ function unstake(uint256 amount) external requirePositiveBalance nonReentrant { _addDebit(msg.sender, amount); _transferFromBridge(msg.sender, amount); emit Unstake(msg.sender, amount); } /** * @dev Add Bonder to allowlist * @param bonder The address being added as a Bonder */ function addBonder(address bonder) external onlyGovernance { require(_isBonder[bonder] == false, "ACT: Address is already bonder"); _isBonder[bonder] = true; emit BonderAdded(bonder); } /** * @dev Remove Bonder from allowlist * @param bonder The address being removed as a Bonder */ function removeBonder(address bonder) external onlyGovernance { require(_isBonder[bonder] == true, "ACT: Address is not bonder"); _isBonder[bonder] = false; emit BonderRemoved(bonder); } /* ========== Internal functions ========== */ function _addCredit(address bonder, uint256 amount) internal { _credit[bonder] = _credit[bonder].add(amount); } function _addDebit(address bonder, uint256 amount) internal { _debit[bonder] = _debit[bonder].add(amount); } } // SPDX-License-Identifier: MIT pragma solidity >0.5.0 <0.8.0; /** * @title Lib_MerkleTree * @author River Keefer */ library Lib_MerkleTree { /********************** * Internal Functions * **********************/ /** * Calculates a merkle root for a list of 32-byte leaf hashes. WARNING: If the number * of leaves passed in is not a power of two, it pads out the tree with zero hashes. * If you do not know the original length of elements for the tree you are verifying, * then this may allow empty leaves past _elements.length to pass a verification check down the line. * Note that the _elements argument is modified, therefore it must not be used again afterwards * @param _elements Array of hashes from which to generate a merkle root. * @return Merkle root of the leaves, with zero hashes for non-powers-of-two (see above). */ function getMerkleRoot( bytes32[] memory _elements ) internal pure returns ( bytes32 ) { require( _elements.length > 0, "Lib_MerkleTree: Must provide at least one leaf hash." ); if (_elements.length == 1) { return _elements[0]; } uint256[16] memory defaults = [ 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563, 0x633dc4d7da7256660a892f8f1604a44b5432649cc8ec5cb3ced4c4e6ac94dd1d, 0x890740a8eb06ce9be422cb8da5cdafc2b58c0a5e24036c578de2a433c828ff7d, 0x3b8ec09e026fdc305365dfc94e189a81b38c7597b3d941c279f042e8206e0bd8, 0xecd50eee38e386bd62be9bedb990706951b65fe053bd9d8a521af753d139e2da, 0xdefff6d330bb5403f63b14f33b578274160de3a50df4efecf0e0db73bcdd3da5, 0x617bdd11f7c0a11f49db22f629387a12da7596f9d1704d7465177c63d88ec7d7, 0x292c23a9aa1d8bea7e2435e555a4a60e379a5a35f3f452bae60121073fb6eead, 0xe1cea92ed99acdcb045a6726b2f87107e8a61620a232cf4d7d5b5766b3952e10, 0x7ad66c0a68c72cb89e4fb4303841966e4062a76ab97451e3b9fb526a5ceb7f82, 0xe026cc5a4aed3c22a58cbd3d2ac754c9352c5436f638042dca99034e83636516, 0x3d04cffd8b46a874edf5cfae63077de85f849a660426697b06a829c70dd1409c, 0xad676aa337a485e4728a0b240d92b3ef7b3c372d06d189322bfd5f61f1e7203e, 0xa2fca4a49658f9fab7aa63289c91b7c7b6c832a6d0e69334ff5b0a3483d09dab, 0x4ebfd9cd7bca2505f7bef59cc1c12ecc708fff26ae4af19abe852afe9e20c862, 0x2def10d13dd169f550f578bda343d9717a138562e0093b380a1120789d53cf10 ]; // Reserve memory space for our hashes. bytes memory buf = new bytes(64); // We'll need to keep track of left and right siblings. bytes32 leftSibling; bytes32 rightSibling; // Number of non-empty nodes at the current depth. uint256 rowSize = _elements.length; // Current depth, counting from 0 at the leaves uint256 depth = 0; // Common sub-expressions uint256 halfRowSize; // rowSize / 2 bool rowSizeIsOdd; // rowSize % 2 == 1 while (rowSize > 1) { halfRowSize = rowSize / 2; rowSizeIsOdd = rowSize % 2 == 1; for (uint256 i = 0; i < halfRowSize; i++) { leftSibling = _elements[(2 * i) ]; rightSibling = _elements[(2 * i) + 1]; assembly { mstore(add(buf, 32), leftSibling ) mstore(add(buf, 64), rightSibling) } _elements[i] = keccak256(buf); } if (rowSizeIsOdd) { leftSibling = _elements[rowSize - 1]; rightSibling = bytes32(defaults[depth]); assembly { mstore(add(buf, 32), leftSibling) mstore(add(buf, 64), rightSibling) } _elements[halfRowSize] = keccak256(buf); } rowSize = halfRowSize + (rowSizeIsOdd ? 1 : 0); depth++; } return _elements[0]; } /** * Verifies a merkle branch for the given leaf hash. Assumes the original length * of leaves generated is a known, correct input, and does not return true for indices * extending past that index (even if _siblings would be otherwise valid.) * @param _root The Merkle root to verify against. * @param _leaf The leaf hash to verify inclusion of. * @param _index The index in the tree of this leaf. * @param _siblings Array of sibline nodes in the inclusion proof, starting from depth 0 (bottom of the tree). * @param _totalLeaves The total number of leaves originally passed into. * @return Whether or not the merkle branch and leaf passes verification. */ function verify( bytes32 _root, bytes32 _leaf, uint256 _index, bytes32[] memory _siblings, uint256 _totalLeaves ) internal pure returns ( bool ) { require( _totalLeaves > 0, "Lib_MerkleTree: Total leaves must be greater than zero." ); require( _index < _totalLeaves, "Lib_MerkleTree: Index out of bounds." ); require( _siblings.length == _ceilLog2(_totalLeaves), "Lib_MerkleTree: Total siblings does not correctly correspond to total leaves." ); bytes32 computedRoot = _leaf; for (uint256 i = 0; i < _siblings.length; i++) { if ((_index & 1) == 1) { computedRoot = keccak256( abi.encodePacked( _siblings[i], computedRoot ) ); } else { computedRoot = keccak256( abi.encodePacked( computedRoot, _siblings[i] ) ); } _index >>= 1; } return _root == computedRoot; } /********************* * Private Functions * *********************/ /** * Calculates the integer ceiling of the log base 2 of an input. * @param _in Unsigned input to calculate the log. * @return ceil(log_base_2(_in)) */ function _ceilLog2( uint256 _in ) private pure returns ( uint256 ) { require( _in > 0, "Lib_MerkleTree: Cannot compute ceil(log_2) of 0." ); if (_in == 1) { return 0; } // Find the highest set bit (will be floor(log_2)). // Borrowed with <3 from https://github.com/ethereum/solidity-examples uint256 val = _in; uint256 highest = 0; for (uint256 i = 128; i >= 1; i >>= 1) { if (val & (uint(1) << i) - 1 << i != 0) { highest += i; val >>= i; } } // Increment by one if this is not a perfect logarithm. if ((uint(1) << highest) != _in) { highest += 1; } return highest; } }// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor () internal { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and make it call a * `private` function that does the actual work. */ modifier nonReentrant() { // On the first call to nonReentrant, _notEntered will be true require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; _; // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } }
File 6 of 8: StateSender
/** Matic network contracts */ pragma solidity ^0.5.2; contract Ownable { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ constructor () internal { _owner = msg.sender; emit OwnershipTransferred(address(0), _owner); } /** * @return the address of the owner. */ function owner() public view returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(isOwner()); _; } /** * @return true if `msg.sender` is the owner of the contract. */ function isOwner() public view returns (bool) { return msg.sender == _owner; } /** * @dev Allows the current owner to relinquish control of the contract. * It will not be possible to call the functions with the `onlyOwner` * modifier anymore. * @notice Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { _transferOwnership(newOwner); } /** * @dev Transfers control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function _transferOwnership(address newOwner) internal { require(newOwner != address(0)); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } library SafeMath { /** * @dev Multiplies two unsigned integers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // 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-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b); return c; } /** * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two unsigned integers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0); return a % b; } } contract StateSender is Ownable { using SafeMath for uint256; uint256 public counter; mapping(address => address) public registrations; event NewRegistration( address indexed user, address indexed sender, address indexed receiver ); event RegistrationUpdated( address indexed user, address indexed sender, address indexed receiver ); event StateSynced( uint256 indexed id, address indexed contractAddress, bytes data ); modifier onlyRegistered(address receiver) { require(registrations[receiver] == msg.sender, "Invalid sender"); _; } function syncState(address receiver, bytes calldata data) external onlyRegistered(receiver) { counter = counter.add(1); emit StateSynced(counter, receiver, data); } // register new contract for state sync function register(address sender, address receiver) public { require( isOwner() || registrations[receiver] == msg.sender, "StateSender.register: Not authorized to register" ); registrations[receiver] = sender; if (registrations[receiver] == address(0)) { emit NewRegistration(msg.sender, sender, receiver); } else { emit RegistrationUpdated(msg.sender, sender, receiver); } } }
File 7 of 8: PolygonMessengerWrapper
// SPDX-License-Identifier: MIT pragma solidity >=0.6.12 <=0.8.9; pragma experimental ABIEncoderV2; interface IMessengerWrapper { function sendCrossDomainMessage(bytes memory _calldata) external; function verifySender(address l1BridgeCaller, bytes memory _data) external; function confirmRoots( bytes32[] calldata rootHashes, uint256[] calldata destinationChainIds, uint256[] calldata totalAmounts, uint256[] calldata rootCommittedAts ) external; } pragma solidity ^0.8.0; import {RLPReader} from "./RLPReader.sol"; library ExitPayloadReader { using RLPReader for bytes; using RLPReader for RLPReader.RLPItem; uint8 constant WORD_SIZE = 32; struct ExitPayload { RLPReader.RLPItem[] data; } struct Receipt { RLPReader.RLPItem[] data; bytes raw; uint256 logIndex; } struct Log { RLPReader.RLPItem data; RLPReader.RLPItem[] list; } struct LogTopics { RLPReader.RLPItem[] data; } // copy paste of private copy() from RLPReader to avoid changing of existing contracts function copy( uint256 src, uint256 dest, uint256 len ) private pure { if (len == 0) return; // copy as many word sizes as possible for (; len >= WORD_SIZE; len -= WORD_SIZE) { assembly { mstore(dest, mload(src)) } src += WORD_SIZE; dest += WORD_SIZE; } // left over bytes. Mask is used to remove unwanted bytes from the word uint256 mask = 256**(WORD_SIZE - len) - 1; assembly { let srcpart := and(mload(src), not(mask)) // zero out src let destpart := and(mload(dest), mask) // retrieve the bytes mstore(dest, or(destpart, srcpart)) } } function toExitPayload(bytes memory data) internal pure returns (ExitPayload memory) { RLPReader.RLPItem[] memory payloadData = data.toRlpItem().toList(); return ExitPayload(payloadData); } function getHeaderNumber(ExitPayload memory payload) internal pure returns (uint256) { return payload.data[0].toUint(); } function getBlockProof(ExitPayload memory payload) internal pure returns (bytes memory) { return payload.data[1].toBytes(); } function getBlockNumber(ExitPayload memory payload) internal pure returns (uint256) { return payload.data[2].toUint(); } function getBlockTime(ExitPayload memory payload) internal pure returns (uint256) { return payload.data[3].toUint(); } function getTxRoot(ExitPayload memory payload) internal pure returns (bytes32) { return bytes32(payload.data[4].toUint()); } function getReceiptRoot(ExitPayload memory payload) internal pure returns (bytes32) { return bytes32(payload.data[5].toUint()); } function getReceipt(ExitPayload memory payload) internal pure returns (Receipt memory receipt) { receipt.raw = payload.data[6].toBytes(); RLPReader.RLPItem memory receiptItem = receipt.raw.toRlpItem(); if (receiptItem.isList()) { // legacy tx receipt.data = receiptItem.toList(); } else { // pop first byte before parsting receipt bytes memory typedBytes = receipt.raw; bytes memory result = new bytes(typedBytes.length - 1); uint256 srcPtr; uint256 destPtr; assembly { srcPtr := add(33, typedBytes) destPtr := add(0x20, result) } copy(srcPtr, destPtr, result.length); receipt.data = result.toRlpItem().toList(); } receipt.logIndex = getReceiptLogIndex(payload); return receipt; } function getReceiptProof(ExitPayload memory payload) internal pure returns (bytes memory) { return payload.data[7].toBytes(); } function getBranchMaskAsBytes(ExitPayload memory payload) internal pure returns (bytes memory) { return payload.data[8].toBytes(); } function getBranchMaskAsUint(ExitPayload memory payload) internal pure returns (uint256) { return payload.data[8].toUint(); } function getReceiptLogIndex(ExitPayload memory payload) internal pure returns (uint256) { return payload.data[9].toUint(); } // Receipt methods function toBytes(Receipt memory receipt) internal pure returns (bytes memory) { return receipt.raw; } function getLog(Receipt memory receipt) internal pure returns (Log memory) { RLPReader.RLPItem memory logData = receipt.data[3].toList()[receipt.logIndex]; return Log(logData, logData.toList()); } // Log methods function getEmitter(Log memory log) internal pure returns (address) { return RLPReader.toAddress(log.list[0]); } function getTopics(Log memory log) internal pure returns (LogTopics memory) { return LogTopics(log.list[1].toList()); } function getData(Log memory log) internal pure returns (bytes memory) { return log.list[2].toBytes(); } function toRlpBytes(Log memory log) internal pure returns (bytes memory) { return log.data.toRlpBytes(); } // LogTopics methods function getField(LogTopics memory topics, uint256 index) internal pure returns (RLPReader.RLPItem memory) { return topics.data[index]; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; library Merkle { function checkMembership( bytes32 leaf, uint256 index, bytes32 rootHash, bytes memory proof ) internal pure returns (bool) { require(proof.length % 32 == 0, "Invalid proof length"); uint256 proofHeight = proof.length / 32; // Proof of size n means, height of the tree is n+1. // In a tree of height n+1, max #leafs possible is 2 ^ n require(index < 2**proofHeight, "Leaf index is too big"); bytes32 proofElement; bytes32 computedHash = leaf; for (uint256 i = 32; i <= proof.length; i += 32) { assembly { proofElement := mload(add(proof, i)) } if (index % 2 == 0) { computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); } else { computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); } index = index / 2; } return computedHash == rootHash; } }// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {RLPReader} from "./RLPReader.sol"; library MerklePatriciaProof { /* * @dev Verifies a merkle patricia proof. * @param value The terminating value in the trie. * @param encodedPath The path in the trie leading to value. * @param rlpParentNodes The rlp encoded stack of nodes. * @param root The root hash of the trie. * @return The boolean validity of the proof. */ function verify( bytes memory value, bytes memory encodedPath, bytes memory rlpParentNodes, bytes32 root ) internal pure returns (bool) { RLPReader.RLPItem memory item = RLPReader.toRlpItem(rlpParentNodes); RLPReader.RLPItem[] memory parentNodes = RLPReader.toList(item); bytes memory currentNode; RLPReader.RLPItem[] memory currentNodeList; bytes32 nodeKey = root; uint256 pathPtr = 0; bytes memory path = _getNibbleArray(encodedPath); if (path.length == 0) { return false; } for (uint256 i = 0; i < parentNodes.length; i++) { if (pathPtr > path.length) { return false; } currentNode = RLPReader.toRlpBytes(parentNodes[i]); if (nodeKey != keccak256(currentNode)) { return false; } currentNodeList = RLPReader.toList(parentNodes[i]); if (currentNodeList.length == 17) { if (pathPtr == path.length) { if (keccak256(RLPReader.toBytes(currentNodeList[16])) == keccak256(value)) { return true; } else { return false; } } uint8 nextPathNibble = uint8(path[pathPtr]); if (nextPathNibble > 16) { return false; } nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[nextPathNibble])); pathPtr += 1; } else if (currentNodeList.length == 2) { uint256 traversed = _nibblesToTraverse(RLPReader.toBytes(currentNodeList[0]), path, pathPtr); if (pathPtr + traversed == path.length) { //leaf node if (keccak256(RLPReader.toBytes(currentNodeList[1])) == keccak256(value)) { return true; } else { return false; } } //extension node if (traversed == 0) { return false; } pathPtr += traversed; nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[1])); } else { return false; } } } function _nibblesToTraverse( bytes memory encodedPartialPath, bytes memory path, uint256 pathPtr ) private pure returns (uint256) { uint256 len = 0; // encodedPartialPath has elements that are each two hex characters (1 byte), but partialPath // and slicedPath have elements that are each one hex character (1 nibble) bytes memory partialPath = _getNibbleArray(encodedPartialPath); bytes memory slicedPath = new bytes(partialPath.length); // pathPtr counts nibbles in path // partialPath.length is a number of nibbles for (uint256 i = pathPtr; i < pathPtr + partialPath.length; i++) { bytes1 pathNibble = path[i]; slicedPath[i - pathPtr] = pathNibble; } if (keccak256(partialPath) == keccak256(slicedPath)) { len = partialPath.length; } else { len = 0; } return len; } // bytes b must be hp encoded function _getNibbleArray(bytes memory b) internal pure returns (bytes memory) { bytes memory nibbles = ""; if (b.length > 0) { uint8 offset; uint8 hpNibble = uint8(_getNthNibbleOfBytes(0, b)); if (hpNibble == 1 || hpNibble == 3) { nibbles = new bytes(b.length * 2 - 1); bytes1 oddNibble = _getNthNibbleOfBytes(1, b); nibbles[0] = oddNibble; offset = 1; } else { nibbles = new bytes(b.length * 2 - 2); offset = 0; } for (uint256 i = offset; i < nibbles.length; i++) { nibbles[i] = _getNthNibbleOfBytes(i - offset + 2, b); } } return nibbles; } function _getNthNibbleOfBytes(uint256 n, bytes memory str) private pure returns (bytes1) { return bytes1(n % 2 == 0 ? uint8(str[n / 2]) / 0x10 : uint8(str[n / 2]) % 0x10); } }/* * @author Hamdi Allam [email protected] * Please reach out with any questions or concerns */ pragma solidity ^0.8.0; library RLPReader { uint8 constant STRING_SHORT_START = 0x80; uint8 constant STRING_LONG_START = 0xb8; uint8 constant LIST_SHORT_START = 0xc0; uint8 constant LIST_LONG_START = 0xf8; uint8 constant WORD_SIZE = 32; struct RLPItem { uint256 len; uint256 memPtr; } struct Iterator { RLPItem item; // Item that's being iterated over. uint256 nextPtr; // Position of the next item in the list. } /* * @dev Returns the next element in the iteration. Reverts if it has not next element. * @param self The iterator. * @return The next element in the iteration. */ function next(Iterator memory self) internal pure returns (RLPItem memory) { require(hasNext(self)); uint256 ptr = self.nextPtr; uint256 itemLength = _itemLength(ptr); self.nextPtr = ptr + itemLength; return RLPItem(itemLength, ptr); } /* * @dev Returns true if the iteration has more elements. * @param self The iterator. * @return true if the iteration has more elements. */ function hasNext(Iterator memory self) internal pure returns (bool) { RLPItem memory item = self.item; return self.nextPtr < item.memPtr + item.len; } /* * @param item RLP encoded bytes */ function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) { uint256 memPtr; assembly { memPtr := add(item, 0x20) } return RLPItem(item.length, memPtr); } /* * @dev Create an iterator. Reverts if item is not a list. * @param self The RLP item. * @return An 'Iterator' over the item. */ function iterator(RLPItem memory self) internal pure returns (Iterator memory) { require(isList(self)); uint256 ptr = self.memPtr + _payloadOffset(self.memPtr); return Iterator(self, ptr); } /* * @param item RLP encoded bytes */ function rlpLen(RLPItem memory item) internal pure returns (uint256) { return item.len; } /* * @param item RLP encoded bytes */ function payloadLen(RLPItem memory item) internal pure returns (uint256) { return item.len - _payloadOffset(item.memPtr); } /* * @param item RLP encoded list in bytes */ function toList(RLPItem memory item) internal pure returns (RLPItem[] memory) { require(isList(item)); uint256 items = numItems(item); RLPItem[] memory result = new RLPItem[](items); uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr); uint256 dataLen; for (uint256 i = 0; i < items; i++) { dataLen = _itemLength(memPtr); result[i] = RLPItem(dataLen, memPtr); memPtr = memPtr + dataLen; } return result; } // @return indicator whether encoded payload is a list. negate this function call for isData. function isList(RLPItem memory item) internal pure returns (bool) { if (item.len == 0) return false; uint8 byte0; uint256 memPtr = item.memPtr; assembly { byte0 := byte(0, mload(memPtr)) } if (byte0 < LIST_SHORT_START) return false; return true; } /* * @dev A cheaper version of keccak256(toRlpBytes(item)) that avoids copying memory. * @return keccak256 hash of RLP encoded bytes. */ function rlpBytesKeccak256(RLPItem memory item) internal pure returns (bytes32) { uint256 ptr = item.memPtr; uint256 len = item.len; bytes32 result; assembly { result := keccak256(ptr, len) } return result; } function payloadLocation(RLPItem memory item) internal pure returns (uint256, uint256) { uint256 offset = _payloadOffset(item.memPtr); uint256 memPtr = item.memPtr + offset; uint256 len = item.len - offset; // data length return (memPtr, len); } /* * @dev A cheaper version of keccak256(toBytes(item)) that avoids copying memory. * @return keccak256 hash of the item payload. */ function payloadKeccak256(RLPItem memory item) internal pure returns (bytes32) { (uint256 memPtr, uint256 len) = payloadLocation(item); bytes32 result; assembly { result := keccak256(memPtr, len) } return result; } /** RLPItem conversions into data types **/ // @returns raw rlp encoding in bytes function toRlpBytes(RLPItem memory item) internal pure returns (bytes memory) { bytes memory result = new bytes(item.len); if (result.length == 0) return result; uint256 ptr; assembly { ptr := add(0x20, result) } copy(item.memPtr, ptr, item.len); return result; } // any non-zero byte is considered true function toBoolean(RLPItem memory item) internal pure returns (bool) { require(item.len == 1); uint256 result; uint256 memPtr = item.memPtr; assembly { result := byte(0, mload(memPtr)) } return result == 0 ? false : true; } function toAddress(RLPItem memory item) internal pure returns (address) { // 1 byte for the length prefix require(item.len == 21); return address(uint160(toUint(item))); } function toUint(RLPItem memory item) internal pure returns (uint256) { require(item.len > 0 && item.len <= 33); uint256 offset = _payloadOffset(item.memPtr); uint256 len = item.len - offset; uint256 result; uint256 memPtr = item.memPtr + offset; assembly { result := mload(memPtr) // shfit to the correct location if neccesary if lt(len, 32) { result := div(result, exp(256, sub(32, len))) } } return result; } // enforces 32 byte length function toUintStrict(RLPItem memory item) internal pure returns (uint256) { // one byte prefix require(item.len == 33); uint256 result; uint256 memPtr = item.memPtr + 1; assembly { result := mload(memPtr) } return result; } function toBytes(RLPItem memory item) internal pure returns (bytes memory) { require(item.len > 0); uint256 offset = _payloadOffset(item.memPtr); uint256 len = item.len - offset; // data length bytes memory result = new bytes(len); uint256 destPtr; assembly { destPtr := add(0x20, result) } copy(item.memPtr + offset, destPtr, len); return result; } /* * Private Helpers */ // @return number of payload items inside an encoded list. function numItems(RLPItem memory item) private pure returns (uint256) { if (item.len == 0) return 0; uint256 count = 0; uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr); uint256 endPtr = item.memPtr + item.len; while (currPtr < endPtr) { currPtr = currPtr + _itemLength(currPtr); // skip over an item count++; } return count; } // @return entire rlp item byte length function _itemLength(uint256 memPtr) private pure returns (uint256) { uint256 itemLen; uint256 byte0; assembly { byte0 := byte(0, mload(memPtr)) } if (byte0 < STRING_SHORT_START) itemLen = 1; else if (byte0 < STRING_LONG_START) itemLen = byte0 - STRING_SHORT_START + 1; else if (byte0 < LIST_SHORT_START) { assembly { let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is memPtr := add(memPtr, 1) // skip over the first byte /* 32 byte word size */ let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len itemLen := add(dataLen, add(byteLen, 1)) } } else if (byte0 < LIST_LONG_START) { itemLen = byte0 - LIST_SHORT_START + 1; } else { assembly { let byteLen := sub(byte0, 0xf7) memPtr := add(memPtr, 1) let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length itemLen := add(dataLen, add(byteLen, 1)) } } return itemLen; } // @return number of bytes until the data function _payloadOffset(uint256 memPtr) private pure returns (uint256) { uint256 byte0; assembly { byte0 := byte(0, mload(memPtr)) } if (byte0 < STRING_SHORT_START) return 0; else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)) return 1; else if (byte0 < LIST_SHORT_START) // being explicit return byte0 - (STRING_LONG_START - 1) + 1; else return byte0 - (LIST_LONG_START - 1) + 1; } /* * @param src Pointer to source * @param dest Pointer to destination * @param len Amount of memory to copy from the source */ function copy( uint256 src, uint256 dest, uint256 len ) private pure { if (len == 0) return; // copy as many word sizes as possible for (; len >= WORD_SIZE; len -= WORD_SIZE) { assembly { mstore(dest, mload(src)) } src += WORD_SIZE; dest += WORD_SIZE; } if (len == 0) return; // left over bytes. Mask is used to remove unwanted bytes from the word uint256 mask = 256**(WORD_SIZE - len) - 1; assembly { let srcpart := and(mload(src), not(mask)) // zero out src let destpart := and(mload(dest), mask) // retrieve the bytes mstore(dest, or(destpart, srcpart)) } } }// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {RLPReader} from "../lib/RLPReader.sol"; import {MerklePatriciaProof} from "../lib/MerklePatriciaProof.sol"; import {Merkle} from "../lib/Merkle.sol"; import "../lib/ExitPayloadReader.sol"; interface IFxStateSender { function sendMessageToChild(address _receiver, bytes calldata _data) external; } contract ICheckpointManager { struct HeaderBlock { bytes32 root; uint256 start; uint256 end; uint256 createdAt; address proposer; } /** * @notice mapping of checkpoint header numbers to block details * @dev These checkpoints are submited by plasma contracts */ mapping(uint256 => HeaderBlock) public headerBlocks; } abstract contract FxBaseRootTunnel { using RLPReader for RLPReader.RLPItem; using Merkle for bytes32; using ExitPayloadReader for bytes; using ExitPayloadReader for ExitPayloadReader.ExitPayload; using ExitPayloadReader for ExitPayloadReader.Log; using ExitPayloadReader for ExitPayloadReader.LogTopics; using ExitPayloadReader for ExitPayloadReader.Receipt; // keccak256(MessageSent(bytes)) bytes32 public constant SEND_MESSAGE_EVENT_SIG = 0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036; // state sender contract IFxStateSender public fxRoot; // root chain manager ICheckpointManager public checkpointManager; // child tunnel contract which receives and sends messages address public fxChildTunnel; // storage to avoid duplicate exits mapping(bytes32 => bool) public processedExits; constructor(address _checkpointManager, address _fxRoot) { checkpointManager = ICheckpointManager(_checkpointManager); fxRoot = IFxStateSender(_fxRoot); } // set fxChildTunnel if not set already function setFxChildTunnel(address _fxChildTunnel) public { require(fxChildTunnel == address(0x0), "FxBaseRootTunnel: CHILD_TUNNEL_ALREADY_SET"); fxChildTunnel = _fxChildTunnel; } /** * @notice Send bytes message to Child Tunnel * @param message bytes message that will be sent to Child Tunnel * some message examples - * abi.encode(tokenId); * abi.encode(tokenId, tokenMetadata); * abi.encode(messageType, messageData); */ function _sendMessageToChild(bytes memory message) internal { fxRoot.sendMessageToChild(fxChildTunnel, message); } function _validateAndExtractMessage(bytes memory inputData) internal returns (bytes memory) { ExitPayloadReader.ExitPayload memory payload = inputData.toExitPayload(); bytes memory branchMaskBytes = payload.getBranchMaskAsBytes(); uint256 blockNumber = payload.getBlockNumber(); // checking if exit has already been processed // unique exit is identified using hash of (blockNumber, branchMask, receiptLogIndex) bytes32 exitHash = keccak256( abi.encodePacked( blockNumber, // first 2 nibbles are dropped while generating nibble array // this allows branch masks that are valid but bypass exitHash check (changing first 2 nibbles only) // so converting to nibble array and then hashing it MerklePatriciaProof._getNibbleArray(branchMaskBytes), payload.getReceiptLogIndex() ) ); require(processedExits[exitHash] == false, "FxRootTunnel: EXIT_ALREADY_PROCESSED"); processedExits[exitHash] = true; ExitPayloadReader.Receipt memory receipt = payload.getReceipt(); ExitPayloadReader.Log memory log = receipt.getLog(); // check child tunnel require(fxChildTunnel == log.getEmitter(), "FxRootTunnel: INVALID_FX_CHILD_TUNNEL"); bytes32 receiptRoot = payload.getReceiptRoot(); // verify receipt inclusion require( MerklePatriciaProof.verify(receipt.toBytes(), branchMaskBytes, payload.getReceiptProof(), receiptRoot), "FxRootTunnel: INVALID_RECEIPT_PROOF" ); // verify checkpoint inclusion _checkBlockMembershipInCheckpoint( blockNumber, payload.getBlockTime(), payload.getTxRoot(), receiptRoot, payload.getHeaderNumber(), payload.getBlockProof() ); ExitPayloadReader.LogTopics memory topics = log.getTopics(); require( bytes32(topics.getField(0).toUint()) == SEND_MESSAGE_EVENT_SIG, // topic0 is event sig "FxRootTunnel: INVALID_SIGNATURE" ); // received message data bytes memory message = abi.decode(log.getData(), (bytes)); // event decodes params again, so decoding bytes to get message return message; } function _checkBlockMembershipInCheckpoint( uint256 blockNumber, uint256 blockTime, bytes32 txRoot, bytes32 receiptRoot, uint256 headerNumber, bytes memory blockProof ) private view returns (uint256) { (bytes32 headerRoot, uint256 startBlock, , uint256 createdAt, ) = checkpointManager.headerBlocks(headerNumber); require( keccak256(abi.encodePacked(blockNumber, blockTime, txRoot, receiptRoot)).checkMembership( blockNumber - startBlock, headerRoot, blockProof ), "FxRootTunnel: INVALID_HEADER" ); return createdAt; } /** * @notice receive message from L2 to L1, validated by proof * @dev This function verifies if the transaction actually happened on child chain * * @param inputData RLP encoded data of the reference tx containing following list of fields * 0 - headerNumber - Checkpoint header block number containing the reference tx * 1 - blockProof - Proof that the block header (in the child chain) is a leaf in the submitted merkle root * 2 - blockNumber - Block number containing the reference tx on child chain * 3 - blockTime - Reference tx block time * 4 - txRoot - Transactions root of block * 5 - receiptRoot - Receipts root of block * 6 - receipt - Receipt of the reference transaction * 7 - receiptProof - Merkle proof of the reference receipt * 8 - branchMask - 32 bits denoting the path of receipt in merkle tree * 9 - receiptLogIndex - Log Index to read from the receipt */ function receiveMessage(bytes memory inputData) public virtual { bytes memory message = _validateAndExtractMessage(inputData); _processMessageFromChild(message); } /** * @notice Process message received from Child Tunnel * @dev function needs to be implemented to handle message as per requirement * This is called by onStateReceive function. * Since it is called via a system call, any event will not be emitted during its execution. * @param message bytes message that was sent from Child Tunnel */ function _processMessageFromChild(bytes memory message) internal virtual; }// SPDX-License-Identifier: MIT pragma solidity >=0.6.12 <=0.8.9; pragma experimental ABIEncoderV2; import "../interfaces/IMessengerWrapper.sol"; contract IL1Bridge { struct TransferBond { address bonder; uint256 createdAt; uint256 totalAmount; uint256 challengeStartTime; address challenger; bool challengeResolved; } uint256 public challengePeriod; mapping(bytes32 => TransferBond) public transferBonds; function getIsBonder(address maybeBonder) public view returns (bool) {} function getTransferRootId(bytes32 rootHash, uint256 totalAmount) public pure returns (bytes32) {} function confirmTransferRoot( uint256 originChainId, bytes32 rootHash, uint256 destinationChainId, uint256 totalAmount, uint256 rootCommittedAt ) external {} } abstract contract MessengerWrapper is IMessengerWrapper { address public immutable l1BridgeAddress; uint256 public immutable l2ChainId; bool public isRootConfirmation = false; constructor(address _l1BridgeAddress, uint256 _l2ChainId) internal { l1BridgeAddress = _l1BridgeAddress; l2ChainId = _l2ChainId; } modifier onlyL1Bridge { require(msg.sender == l1BridgeAddress, "MW: Sender must be the L1 Bridge"); _; } modifier rootConfirmation { isRootConfirmation = true; _; isRootConfirmation = false; } /** * @dev Confirm roots that have bonded on L1 and passed the challenge period with no challenge * @param rootHashes The root hashes to confirm * @param destinationChainIds The destinationChainId of the roots to confirm * @param totalAmounts The totalAmount of the roots to confirm * @param rootCommittedAts The rootCommittedAt of the roots to confirm */ function confirmRoots ( bytes32[] calldata rootHashes, uint256[] calldata destinationChainIds, uint256[] calldata totalAmounts, uint256[] calldata rootCommittedAts ) external override rootConfirmation { IL1Bridge l1Bridge = IL1Bridge(l1BridgeAddress); require(l1Bridge.getIsBonder(msg.sender), "MW: Sender must be a bonder"); require(rootHashes.length == totalAmounts.length, "MW: rootHashes and totalAmounts must be the same length"); uint256 challengePeriod = l1Bridge.challengePeriod(); for (uint256 i = 0; i < rootHashes.length; i++) { bool canConfirm = canConfirmRoot(l1Bridge, rootHashes[i], totalAmounts[i], challengePeriod); require(canConfirm, "MW: Root cannot be confirmed"); l1Bridge.confirmTransferRoot( l2ChainId, rootHashes[i], destinationChainIds[i], totalAmounts[i], rootCommittedAts[i] ); } } function canConfirmRoot (IL1Bridge l1Bridge, bytes32 rootHash, uint256 totalAmount, uint256 challengePeriod) public view returns (bool) { bytes32 transferRootId = l1Bridge.getTransferRootId(rootHash, totalAmount); (,uint256 createdAt,,uint256 challengeStartTime,,) = l1Bridge.transferBonds(transferRootId); uint256 timeSinceBondCreation = block.timestamp - createdAt; if ( createdAt != 0 && challengeStartTime == 0 && timeSinceBondCreation > challengePeriod ) { return true; } return false; } } // SPDX-License-Identifier: MIT // @unsupported: ovm pragma solidity 0.8.9; pragma experimental ABIEncoderV2; import "../polygon/tunnel/FxBaseRootTunnel.sol"; import "./MessengerWrapper.sol"; /** * @dev A MessengerWrapper for Polygon - https://docs.matic.network/docs * @notice Deployed on layer-1 */ contract PolygonMessengerWrapper is FxBaseRootTunnel, MessengerWrapper { constructor( address _l1BridgeAddress, address _checkpointManager, address _fxRoot, address _fxChildTunnel, uint256 _l2ChainId ) public MessengerWrapper(_l1BridgeAddress, _l2ChainId) FxBaseRootTunnel(_checkpointManager, _fxRoot) { setFxChildTunnel(_fxChildTunnel); } /** * @dev Sends a message to the l2MessengerProxy from layer-1 * @param _calldata The data that l2MessengerProxy will be called with * @notice The msg.sender is sent to the L2_PolygonMessengerProxy and checked there. */ function sendCrossDomainMessage(bytes memory _calldata) public override { _sendMessageToChild( abi.encode(msg.sender, _calldata) ); } function verifySender(address l1BridgeCaller, bytes memory /*_data*/) public view override { if (isRootConfirmation) return; require(l1BridgeCaller == address(this), "L1_PLGN_WPR: Caller must be this contract"); } function _processMessageFromChild(bytes memory message) internal override { (bool success,) = l1BridgeAddress.call(message); require(success, "L1_PLGN_WPR: Call to L1 Bridge failed"); } }
File 8 of 8: FxRoot
// SPDX-License-Identifier: MIT pragma solidity 0.7.3; interface IStateSender { function syncState(address receiver, bytes calldata data) external; } interface IFxStateSender { function sendMessageToChild(address _receiver, bytes calldata _data) external; } /** * @title FxRoot root contract for fx-portal */ contract FxRoot is IFxStateSender { IStateSender public stateSender; address public fxChild; constructor(address _stateSender) { stateSender = IStateSender(_stateSender); } function setFxChild(address _fxChild) public { require(fxChild == address(0x0)); fxChild = _fxChild; } function sendMessageToChild(address _receiver, bytes calldata _data) public override { bytes memory data = abi.encode(msg.sender, _receiver, _data); stateSender.syncState(fxChild, data); } }