diff --git a/helix-contract/address/eth2arbi-ln-dev.json b/helix-contract/address/eth2arbi-ln-dev.json index ebb59948..bc3a1c59 100644 --- a/helix-contract/address/eth2arbi-ln-dev.json +++ b/helix-contract/address/eth2arbi-ln-dev.json @@ -1,7 +1,7 @@ { "ethereum2arbitrumLnV2-goerli": { "LnBridgeProxyAdmin": "0x3F3eDBda6124462a09E071c5D90e072E0d5d4ed4", - "LnBridgeLogic": "0xB78eA02970801506eE5333E9659eAe95a1ee5f80", + "LnBridgeLogic": "0x26F3925F0cbcf79bF8Ca9041347327b82c55916b", "LnBridgeProxy": "0xcD86cf37a4Dc6f78B4899232E7dD1b5c8130EFDA", "Ring": "0x1836BAFa3016Dd5Ce543D0F7199cB858ec69F41E", "USDC": "0xd35CCeEAD182dcee0F148EbaC9447DA2c4D449c4", diff --git a/helix-contract/contracts/ln/base/LnDefaultBridgeSource.sol b/helix-contract/contracts/ln/base/LnDefaultBridgeSource.sol index 1005e945..a4b017f2 100644 --- a/helix-contract/contracts/ln/base/LnDefaultBridgeSource.sol +++ b/helix-contract/contracts/ln/base/LnDefaultBridgeSource.sol @@ -278,7 +278,7 @@ contract LnDefaultBridgeSource is LnBridgeHelper { uint112 targetAmount = _sourceAmountToTargetAmount(tokenInfo, amount); message = _encodeWithdrawCall( providerInfo.lastTransferId, - providerInfo.withdrawNonce, + providerInfo.withdrawNonce + 1, msg.sender, sourceToken, tokenInfo.targetToken, diff --git a/helix-contract/deploy/deploy_eth2arbi_ln.js b/helix-contract/deploy/deploy_eth2arbi_ln.js index 2e6d9136..3afc8c7e 100644 --- a/helix-contract/deploy/deploy_eth2arbi_ln.js +++ b/helix-contract/deploy/deploy_eth2arbi_ln.js @@ -362,10 +362,9 @@ async function main() { const ringOnEthereum = await ethers.getContractAt("Erc20", ringEthereumAddress, ethereumWallet); //await ringOnEthereum.approve(ethereumLnBridgeAddress, ethers.utils.parseEther("10000000")); - const amount1 = ethers.utils.parseEther("30"); + const amount1 = ethers.utils.parseEther("60"); // lock - /* await transferAndLockMargin( ethereumWallet, ethereumLnBridgeAddress, @@ -374,11 +373,10 @@ async function main() { ringArbitrumAddress, amount1, ethereumWallet.address, - 0, + 5, ); console.log("transfer and lock margin 1 successed"); return; - */ // relay // query: lastTransferId on arbitrum @@ -445,7 +443,7 @@ main() }); /* -ethereumLnBridgeAddressLogic = 0xB78eA02970801506eE5333E9659eAe95a1ee5f80 +ethereumLnBridgeAddressLogic = 0x26F3925F0cbcf79bF8Ca9041347327b82c55916b ethereumLnBridgeAddressProxy = 0xcD86cf37a4Dc6f78B4899232E7dD1b5c8130EFDA arbitrumLnBridgeAddressLogic = 0xC91aff6adA5e743Ae89589126AE4521eB2ec47f2 arbitrumLnBridgeAddressProxy = 0x4112c9d474951246fBC2B4D868D247e714698aE1 diff --git a/helix-contract/flatten/lnv2/Eth2ArbSource.sol b/helix-contract/flatten/lnv2/Eth2ArbSource.sol index 235fab2c..356d5d5b 100644 --- a/helix-contract/flatten/lnv2/Eth2ArbSource.sol +++ b/helix-contract/flatten/lnv2/Eth2ArbSource.sol @@ -14,595 +14,921 @@ * '----------------' '----------------' '----------------' '----------------' '----------------' ' * * - * 7/31/2023 + * 8/7/2023 **/ pragma solidity ^0.8.10; -// File @zeppelin-solidity/contracts/token/ERC20/IERC20.sol@v4.7.3 +// File @zeppelin-solidity/contracts/access/IAccessControl.sol@v4.7.3 // License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) +// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) /** - * @dev Interface of the ERC20 standard as defined in the EIP. + * @dev External interface of AccessControl declared to support ERC165 detection. */ -interface IERC20 { +interface IAccessControl { /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). + * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` * - * Note that `value` may be zero. + * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite + * {RoleAdminChanged} not being emitted signaling this. + * + * _Available since v3.1._ */ - event Transfer(address indexed from, address indexed to, uint256 value); + event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. + * @dev Emitted when `account` is granted `role`. + * + * `sender` is the account that originated the contract call, an admin role + * bearer except when using {AccessControl-_setupRole}. */ - event Approval(address indexed owner, address indexed spender, uint256 value); + event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); /** - * @dev Returns the amount of tokens in existence. + * @dev Emitted when `account` is revoked `role`. + * + * `sender` is the account that originated the contract call: + * - if using `revokeRole`, it is the admin role bearer + * - if using `renounceRole`, it is the role bearer (i.e. `account`) */ - function totalSupply() external view returns (uint256); + event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); /** - * @dev Returns the amount of tokens owned by `account`. + * @dev Returns `true` if `account` has been granted `role`. */ - function balanceOf(address account) external view returns (uint256); + function hasRole(bytes32 role, address account) external view returns (bool); /** - * @dev Moves `amount` tokens from the caller's account to `to`. - * - * Returns a boolean value indicating whether the operation succeeded. + * @dev Returns the admin role that controls `role`. See {grantRole} and + * {revokeRole}. * - * Emits a {Transfer} event. + * To change a role's admin, use {AccessControl-_setRoleAdmin}. */ - function transfer(address to, uint256 amount) external returns (bool); + function getRoleAdmin(bytes32 role) external view returns (bytes32); /** - * @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. + * @dev Grants `role` to `account`. * - * This value changes when {approve} or {transferFrom} are called. + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. */ - function allowance(address owner, address spender) external view returns (uint256); + function grantRole(bytes32 role, address account) external; /** - * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * @dev Revokes `role` from `account`. * - * Returns a boolean value indicating whether the operation succeeded. + * If `account` had been granted `role`, emits a {RoleRevoked} event. * - * 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 + * Requirements: * - * Emits an {Approval} event. + * - the caller must have ``role``'s admin role. */ - function approve(address spender, uint256 amount) external returns (bool); + function revokeRole(bytes32 role, address account) external; /** - * @dev Moves `amount` tokens from `from` to `to` using the - * allowance mechanism. `amount` is then deducted from the caller's - * allowance. + * @dev Revokes `role` from the calling account. * - * Returns a boolean value indicating whether the operation succeeded. + * Roles are often managed via {grantRole} and {revokeRole}: this function's + * purpose is to provide a mechanism for accounts to lose their privileges + * if they are compromised (such as when a trusted device is misplaced). * - * Emits a {Transfer} event. + * If the calling account had been granted `role`, emits a {RoleRevoked} + * event. + * + * Requirements: + * + * - the caller must be `account`. */ - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool); + function renounceRole(bytes32 role, address account) external; } -// File contracts/ln/base/LnBridgeHelper.sol +// File @zeppelin-solidity/contracts/access/IAccessControlEnumerable.sol@v4.7.3 // License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol) -contract LnBridgeHelper { - bytes32 constant public INIT_SLASH_TRANSFER_ID = bytes32(uint256(1)); - struct TransferParameter { - bytes32 previousTransferId; - address provider; - address sourceToken; - address targetToken; - uint112 amount; - uint64 timestamp; - address receiver; - } +/** + * @dev External interface of AccessControlEnumerable declared to support ERC165 detection. + */ +interface IAccessControlEnumerable is IAccessControl { + /** + * @dev Returns one of the accounts that have `role`. `index` must be a + * value between 0 and {getRoleMemberCount}, non-inclusive. + * + * Role bearers are not sorted in any particular way, and their ordering may + * change at any point. + * + * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure + * you perform all queries on the same block. See the following + * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] + * for more information. + */ + function getRoleMember(bytes32 role, uint256 index) external view returns (address); - function _safeTransfer( - address token, - address receiver, - uint256 amount - ) internal { - (bool success, bytes memory data) = token.call(abi.encodeWithSelector( - IERC20.transfer.selector, - receiver, - amount - )); - require(success && (data.length == 0 || abi.decode(data, (bool))), "lnBridgeHelper:transfer token failed"); - } + /** + * @dev Returns the number of accounts that have `role`. Can be used + * together with {getRoleMember} to enumerate all bearers of a role. + */ + function getRoleMemberCount(bytes32 role) external view returns (uint256); +} - function _safeTransferFrom( - address token, - address sender, - address receiver, - uint256 amount - ) internal { - (bool success, bytes memory data) = token.call(abi.encodeWithSelector( - IERC20.transferFrom.selector, - sender, - receiver, - amount - )); - require(success && (data.length == 0 || abi.decode(data, (bool))), "lnBridgeHelper:transferFrom token failed"); - } +// File @zeppelin-solidity/contracts/utils/Context.sol@v4.7.3 +// License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) - function getProviderKey(address provider, address sourceToken) pure public returns(bytes32) { - return keccak256(abi.encodePacked( - provider, - sourceToken - )); + +/** + * @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 getDefaultProviderKey(address provider, address sourceToken, address targetToken) pure public returns(bytes32) { - return keccak256(abi.encodePacked( - provider, - sourceToken, - targetToken - )); + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; } } -// File contracts/ln/interface/ILnDefaultBridgeTarget.sol +// File @zeppelin-solidity/contracts/utils/Strings.sol@v4.7.3 // License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol) -interface ILnDefaultBridgeTarget { - function slash( - LnBridgeHelper.TransferParameter memory params, - address slasher, - uint112 fee, - uint112 penalty - ) external; - - function withdraw( - bytes32 lastTransferId, - uint64 withdrawNonce, - address provider, - address sourceToken, - address targetToken, - uint112 amount - ) external; -} +/** + * @dev String operations. + */ +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + uint8 private constant _ADDRESS_LENGTH = 20; -// File contracts/ln/base/LnDefaultBridgeSource.sol -// License-Identifier: MIT + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } -/// @title LnPositiveBridgeSource -/// @notice LnPositiveBridgeSource is a contract to help user transfer token to liquidity node and generate proof, -/// then the liquidity node must transfer the same amount of the token to the user on target chain. -/// Otherwise if timeout the slasher can send a slash request message to target chain, then force transfer from lnProvider's margin to the user. -/// @dev See https://github.com/helix-bridge/contracts/tree/master/helix-contract -contract LnDefaultBridgeSource is LnBridgeHelper { - // the time(seconds) for liquidity provider to delivery message - // if timeout, slasher can work. - uint256 constant public MIN_SLASH_TIMESTAMP = 30 * 60; - // liquidity fee base rate - // liquidityFee = liquidityFeeRate / LIQUIDITY_FEE_RATE_BASE * sendAmount - uint256 constant public LIQUIDITY_FEE_RATE_BASE = 100000; - // max transfer amount one time - uint256 constant public MAX_TRANSFER_AMOUNT = type(uint112).max; - // the registered token info - // sourceToken and targetToken is the pair of erc20 token addresses - // if sourceToken == address(0), then it's native token - // if targetToken == address(0), then remote is native token - // * `protocolFee` is the protocol fee charged by system - // * `penaltyLnCollateral` is penalty from lnProvider when the transfer slashed, if we adjust this value, it'll not affect the old transfers. - struct TokenInfo { - address targetToken; - uint112 protocolFee; - uint112 penaltyLnCollateral; - uint8 sourceDecimals; - uint8 targetDecimals; - bool isRegistered; + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); } - // provider fee is paid to liquidity node's account - // the fee is charged by the same token that user transfered - // providerFee = baseFee + liquidityFeeRate/LIQUIDITY_FEE_RATE_BASE * sendAmount - struct LnProviderFee { - uint112 baseFee; - uint8 liquidityFeeRate; + /** + * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); } - - struct LnProviderInfo { - LnProviderFee fee; - // we use this nonce to generate the unique withdraw id - uint64 withdrawNonce; - bytes32 lastTransferId; +} + +// File @zeppelin-solidity/contracts/utils/introspection/IERC165.sol@v4.7.3 +// License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +// File @zeppelin-solidity/contracts/utils/introspection/ERC165.sol@v4.7.3 +// License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) + + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + * + * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. + */ +abstract contract ERC165 is IERC165 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC165).interfaceId; } - // the Snapshot is the state of the token bridge when user prepare to transfer across chains. - // If the snapshot updated when the across chain transfer confirmed, it will - // 1. if lastTransferId or withdrawNonce updated, revert - // 2. if totalFee increase, revert - // 3. if totalFee decrease, success - struct Snapshot { - address provider; - address sourceToken; - bytes32 transferId; - uint112 totalFee; - uint64 withdrawNonce; +} + +// File @zeppelin-solidity/contracts/access/AccessControl.sol@v4.7.3 +// License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (access/AccessControl.sol) + + + + + +/** + * @dev Contract module that allows children to implement role-based access + * control mechanisms. This is a lightweight version that doesn't allow enumerating role + * members except through off-chain means by accessing the contract event logs. Some + * applications may benefit from on-chain enumerability, for those cases see + * {AccessControlEnumerable}. + * + * Roles are referred to by their `bytes32` identifier. These should be exposed + * in the external API and be unique. The best way to achieve this is by + * using `public constant` hash digests: + * + * ``` + * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); + * ``` + * + * Roles can be used to represent a set of permissions. To restrict access to a + * function call, use {hasRole}: + * + * ``` + * function foo() public { + * require(hasRole(MY_ROLE, msg.sender)); + * ... + * } + * ``` + * + * Roles can be granted and revoked dynamically via the {grantRole} and + * {revokeRole} functions. Each role has an associated admin role, and only + * accounts that have a role's admin role can call {grantRole} and {revokeRole}. + * + * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means + * that only accounts with this role will be able to grant or revoke other + * roles. More complex role relationships can be created by using + * {_setRoleAdmin}. + * + * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to + * grant and revoke this role. Extra precautions should be taken to secure + * accounts that have been granted it. + */ +abstract contract AccessControl is Context, IAccessControl, ERC165 { + struct RoleData { + mapping(address => bool) members; + bytes32 adminRole; } - // lock info - // the fee and penalty is the state of the transfer confirmed - struct LockInfo { - uint112 fee; - uint112 penalty; - bool isLocked; + mapping(bytes32 => RoleData) private _roles; + + bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + + /** + * @dev Modifier that checks that an account has a specific role. Reverts + * with a standardized message including the required role. + * + * The format of the revert reason is given by the following regular expression: + * + * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ + * + * _Available since v4.1._ + */ + modifier onlyRole(bytes32 role) { + _checkRole(role); + _; } - // sourceToken => token info - mapping(address=>TokenInfo) public tokenInfos; - // providerKey => provider info - mapping(bytes32=>LnProviderInfo) public lnProviders; - // transferId => lock info - mapping(bytes32=>LockInfo) public lockInfos; - address public protocolFeeReceiver; + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId); + } - event TokenLocked( - bytes32 transferId, - address provider, - address sourceToken, - uint112 amount, - uint112 fee, - address receiver); - event LnProviderUpdated(address provider, address sourceToken, uint112 baseFee, uint8 liquidityfeeRate); + /** + * @dev Returns `true` if `account` has been granted `role`. + */ + function hasRole(bytes32 role, address account) public view virtual override returns (bool) { + return _roles[role].members[account]; + } - // protocolFeeReceiver is the protocol fee reciever, we don't use the contract itself as the receiver - function _setFeeReceiver(address _feeReceiver) internal { - require(_feeReceiver != address(this), "invalid system fee receiver"); - protocolFeeReceiver = _feeReceiver; + /** + * @dev Revert with a standard message if `_msgSender()` is missing `role`. + * Overriding this function changes the behavior of the {onlyRole} modifier. + * + * Format of the revert message is described in {_checkRole}. + * + * _Available since v4.6._ + */ + function _checkRole(bytes32 role) internal view virtual { + _checkRole(role, _msgSender()); } - // register or update token info, it can be only called by contract owner - // source token can only map a unique target token on target chain - function _setTokenInfo( - address _sourceToken, - address _targetToken, - uint112 _protocolFee, - uint112 _penaltyLnCollateral, - uint8 _sourceDecimals, - uint8 _targetDecimals - ) internal { - tokenInfos[_sourceToken] = TokenInfo( - _targetToken, - _protocolFee, - _penaltyLnCollateral, - _sourceDecimals, - _targetDecimals, - true - ); + /** + * @dev Revert with a standard message if `account` is missing `role`. + * + * The format of the revert reason is given by the following regular expression: + * + * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ + */ + function _checkRole(bytes32 role, address account) internal view virtual { + if (!hasRole(role, account)) { + revert( + string( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(uint160(account), 20), + " is missing role ", + Strings.toHexString(uint256(role), 32) + ) + ) + ); + } } - // lnProvider register - // 1. set fee on source chain - // 2. deposit margin on target chain - function setProviderFee( - address sourceToken, - uint112 baseFee, - uint8 liquidityFeeRate - ) external { - TokenInfo memory tokenInfo = tokenInfos[sourceToken]; - require(tokenInfo.isRegistered, "token not registered"); - bytes32 providerKey = getDefaultProviderKey(msg.sender, sourceToken, tokenInfo.targetToken); - LnProviderFee memory providerFee = LnProviderFee(baseFee, liquidityFeeRate); + /** + * @dev Returns the admin role that controls `role`. See {grantRole} and + * {revokeRole}. + * + * To change a role's admin, use {_setRoleAdmin}. + */ + function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) { + return _roles[role].adminRole; + } - // we only update the field fee of the provider info - // if the provider has not been registered, then this line will register, otherwise update fee - lnProviders[providerKey].fee = providerFee; + /** + * @dev Grants `role` to `account`. + * + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + * + * May emit a {RoleGranted} event. + */ + function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { + _grantRole(role, account); + } + + /** + * @dev Revokes `role` from `account`. + * + * If `account` had been granted `role`, emits a {RoleRevoked} event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + * + * May emit a {RoleRevoked} event. + */ + function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { + _revokeRole(role, account); + } + + /** + * @dev Revokes `role` from the calling account. + * + * Roles are often managed via {grantRole} and {revokeRole}: this function's + * purpose is to provide a mechanism for accounts to lose their privileges + * if they are compromised (such as when a trusted device is misplaced). + * + * If the calling account had been revoked `role`, emits a {RoleRevoked} + * event. + * + * Requirements: + * + * - the caller must be `account`. + * + * May emit a {RoleRevoked} event. + */ + function renounceRole(bytes32 role, address account) public virtual override { + require(account == _msgSender(), "AccessControl: can only renounce roles for self"); + + _revokeRole(role, account); + } + + /** + * @dev Grants `role` to `account`. + * + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. Note that unlike {grantRole}, this function doesn't perform any + * checks on the calling account. + * + * May emit a {RoleGranted} event. + * + * [WARNING] + * ==== + * This function should only be called from the constructor when setting + * up the initial roles for the system. + * + * Using this function in any other way is effectively circumventing the admin + * system imposed by {AccessControl}. + * ==== + * + * NOTE: This function is deprecated in favor of {_grantRole}. + */ + function _setupRole(bytes32 role, address account) internal virtual { + _grantRole(role, account); + } + + /** + * @dev Sets `adminRole` as ``role``'s admin role. + * + * Emits a {RoleAdminChanged} event. + */ + function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { + bytes32 previousAdminRole = getRoleAdmin(role); + _roles[role].adminRole = adminRole; + emit RoleAdminChanged(role, previousAdminRole, adminRole); + } + + /** + * @dev Grants `role` to `account`. + * + * Internal function without access restriction. + * + * May emit a {RoleGranted} event. + */ + function _grantRole(bytes32 role, address account) internal virtual { + if (!hasRole(role, account)) { + _roles[role].members[account] = true; + emit RoleGranted(role, account, _msgSender()); + } + } + + /** + * @dev Revokes `role` from `account`. + * + * Internal function without access restriction. + * + * May emit a {RoleRevoked} event. + */ + function _revokeRole(bytes32 role, address account) internal virtual { + if (hasRole(role, account)) { + _roles[role].members[account] = false; + emit RoleRevoked(role, account, _msgSender()); + } + } +} + +// File @zeppelin-solidity/contracts/utils/structs/EnumerableSet.sol@v4.7.3 +// License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (utils/structs/EnumerableSet.sol) + + +/** + * @dev Library for managing + * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive + * types. + * + * Sets have the following properties: + * + * - Elements are added, removed, and checked for existence in constant time + * (O(1)). + * - Elements are enumerated in O(n). No guarantees are made on the ordering. + * + * ``` + * contract Example { + * // Add the library methods + * using EnumerableSet for EnumerableSet.AddressSet; + * + * // Declare a set state variable + * EnumerableSet.AddressSet private mySet; + * } + * ``` + * + * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) + * and `uint256` (`UintSet`) are supported. + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an array of EnumerableSet. + * ==== + */ +library EnumerableSet { + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Set type with + // bytes32 values. + // The Set implementation uses private functions, and user-facing + // implementations (such as AddressSet) are just wrappers around the + // underlying Set. + // This means that we can only create new EnumerableSets for types that fit + // in bytes32. - emit LnProviderUpdated(msg.sender, sourceToken, baseFee, liquidityFeeRate); + struct Set { + // Storage of set values + bytes32[] _values; + // Position of the value in the `values` array, plus 1 because index 0 + // means a value is not in the set. + mapping(bytes32 => uint256) _indexes; } - function calculateProviderFee(LnProviderFee memory fee, uint112 amount) internal pure returns(uint256) { - return uint256(fee.baseFee) + uint256(fee.liquidityFeeRate) * uint256(amount) / LIQUIDITY_FEE_RATE_BASE; + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function _add(Set storage set, bytes32 value) private returns (bool) { + if (!_contains(set, value)) { + set._values.push(value); + // The value is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + set._indexes[value] = set._values.length; + return true; + } else { + return false; + } } - // the fee user should paid when transfer. - // totalFee = providerFee + protocolFee - function totalFee(address provider, address sourceToken, uint112 amount) external view returns(uint256) { - TokenInfo memory tokenInfo = tokenInfos[sourceToken]; - bytes32 providerKey = getDefaultProviderKey(provider, sourceToken, tokenInfo.targetToken); - LnProviderInfo memory providerInfo = lnProviders[providerKey]; - uint256 providerFee = calculateProviderFee(providerInfo.fee, amount); - return providerFee + tokenInfo.protocolFee; - } + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function _remove(Set storage set, bytes32 value) private returns (bool) { + // We read and store the value's index to prevent multiple reads from the same storage slot + uint256 valueIndex = set._indexes[value]; - // This function transfers tokens from the user to LnProvider and generates a proof on the source chain. - // The snapshot represents the state of the LN bridge for this LnProvider, obtained by the off-chain indexer. - // If the chain state is updated and does not match the snapshot state, the transaction will be reverted. - // 1. the state(lastTransferId, fee, withdrawNonce) must match snapshot - // 2. transferId not exist - function transferAndLockMargin( - Snapshot calldata snapshot, - uint112 amount, - address receiver - ) external payable { - require(amount > 0, "invalid amount"); + if (valueIndex != 0) { + // Equivalent to contains(set, value) + // To delete an element from the _values array in O(1), we swap the element to delete with the last one in + // the array, and then remove the last element (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. - TokenInfo memory tokenInfo = tokenInfos[snapshot.sourceToken]; - require(tokenInfo.isRegistered, "token not registered"); - - bytes32 providerKey = getDefaultProviderKey(snapshot.provider, snapshot.sourceToken, tokenInfo.targetToken); + uint256 toDeleteIndex = valueIndex - 1; + uint256 lastIndex = set._values.length - 1; - LnProviderInfo memory providerInfo = lnProviders[providerKey]; - uint256 providerFee = calculateProviderFee(providerInfo.fee, amount); + if (lastIndex != toDeleteIndex) { + bytes32 lastValue = set._values[lastIndex]; - // the chain state not match snapshot - require(providerInfo.lastTransferId == snapshot.transferId, "snapshot expired:transfer"); - require(snapshot.withdrawNonce == providerInfo.withdrawNonce, "snapshot expired:withdraw"); - require(snapshot.totalFee >= providerFee + tokenInfo.protocolFee && providerFee > 0, "fee is invalid"); - - uint112 targetAmount = _sourceAmountToTargetAmount(tokenInfo, uint256(amount)); - bytes32 transferId = keccak256(abi.encodePacked( - snapshot.transferId, - snapshot.provider, - snapshot.sourceToken, - tokenInfo.targetToken, - receiver, - uint64(block.timestamp), - targetAmount - )); - require(!lockInfos[transferId].isLocked, "transferId exist"); - // if the transfer refund, then the fee and penalty should be given to slasher, but the protocol fee is ignored - // and we use the penalty value configure at the moment transfer confirmed - lockInfos[transferId] = LockInfo(snapshot.totalFee, tokenInfo.penaltyLnCollateral, true); + // Move the last value to the index where the value to delete is + set._values[toDeleteIndex] = lastValue; + // Update the index for the moved value + set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex + } - // update the state to prevent other transfers using the same snapshot - lnProviders[providerKey].lastTransferId = transferId; + // Delete the slot where the moved value was stored + set._values.pop(); - if (snapshot.sourceToken == address(0)) { - require(amount + snapshot.totalFee == msg.value, "amount unmatched"); - payable(snapshot.provider).transfer(amount + providerFee); - if (tokenInfo.protocolFee > 0) { - payable(protocolFeeReceiver).transfer(tokenInfo.protocolFee); - } - uint256 refund = snapshot.totalFee - tokenInfo.protocolFee - providerFee; - if ( refund > 0 ) { - payable(msg.sender).transfer(refund); - } + // Delete the index for the deleted slot + delete set._indexes[value]; + + return true; } else { - _safeTransferFrom( - snapshot.sourceToken, - msg.sender, - snapshot.provider, - amount + providerFee - ); - if (tokenInfo.protocolFee > 0) { - _safeTransferFrom( - snapshot.sourceToken, - msg.sender, - protocolFeeReceiver, - tokenInfo.protocolFee - ); - } + return false; } - emit TokenLocked( - transferId, - snapshot.provider, - snapshot.sourceToken, - targetAmount, - uint112(providerFee), - receiver); } - function _sourceAmountToTargetAmount( - TokenInfo memory tokenInfo, - uint256 amount - ) internal pure returns(uint112) { - uint256 targetAmount = amount * 10**tokenInfo.targetDecimals / 10**tokenInfo.sourceDecimals; - require(targetAmount < MAX_TRANSFER_AMOUNT, "overflow amount"); - return uint112(targetAmount); + /** + * @dev Returns true if the value is in the set. O(1). + */ + function _contains(Set storage set, bytes32 value) private view returns (bool) { + return set._indexes[value] != 0; } - function _slashAndRemoteRelease( - TransferParameter memory params, - bytes32 expectedTransferId - ) internal view returns(bytes memory message) { - require(block.timestamp > params.timestamp + MIN_SLASH_TIMESTAMP, "invalid timestamp"); - TokenInfo memory tokenInfo = tokenInfos[params.sourceToken]; - require(tokenInfo.isRegistered, "token not registered"); - uint112 targetAmount = _sourceAmountToTargetAmount(tokenInfo, uint256(params.amount)); - - bytes32 transferId = keccak256(abi.encodePacked( - params.previousTransferId, - params.provider, - params.sourceToken, - params.targetToken, - params.receiver, - params.timestamp, - targetAmount - )); - require(expectedTransferId == transferId, "expected transfer id not match"); - LockInfo memory lockInfo = lockInfos[transferId]; - require(lockInfo.isLocked, "lock info not match"); - uint112 targetFee = _sourceAmountToTargetAmount(tokenInfo, lockInfo.fee); - uint112 targetPenalty = _sourceAmountToTargetAmount(tokenInfo, lockInfo.penalty); - - message = _encodeSlashCall( - params, - msg.sender, - targetFee, - targetPenalty - ); + /** + * @dev Returns the number of values on the set. O(1). + */ + function _length(Set storage set) private view returns (uint256) { + return set._values.length; } - function _withdrawMargin( - address sourceToken, - uint112 amount - ) internal returns(bytes memory message) { - TokenInfo memory tokenInfo = tokenInfos[sourceToken]; - require(tokenInfo.isRegistered, "token not registered"); + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Set storage set, uint256 index) private view returns (bytes32) { + return set._values[index]; + } - bytes32 providerKey = getDefaultProviderKey(msg.sender, sourceToken, tokenInfo.targetToken); - LnProviderInfo memory providerInfo = lnProviders[providerKey]; - lnProviders[providerKey].withdrawNonce = providerInfo.withdrawNonce + 1; - uint112 targetAmount = _sourceAmountToTargetAmount(tokenInfo, amount); - message = _encodeWithdrawCall( - providerInfo.lastTransferId, - providerInfo.withdrawNonce, - msg.sender, - sourceToken, - tokenInfo.targetToken, - targetAmount - ); + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function _values(Set storage set) private view returns (bytes32[] memory) { + return set._values; } - function _encodeSlashCall( - TransferParameter memory params, - address slasher, - uint112 fee, - uint112 penalty - ) internal pure returns(bytes memory message) { - return abi.encodeWithSelector( - ILnDefaultBridgeTarget.slash.selector, - params, - slasher, - fee, - penalty - ); + // Bytes32Set + + struct Bytes32Set { + Set _inner; } - function _encodeWithdrawCall( - bytes32 lastTransferId, - uint64 withdrawNonce, - address provider, - address sourceToken, - address targetToken, - uint112 amount - ) internal pure returns(bytes memory message) { - return abi.encodeWithSelector( - ILnDefaultBridgeTarget.withdraw.selector, - lastTransferId, - withdrawNonce, - provider, - sourceToken, - targetToken, - amount - ); + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _add(set._inner, value); } -} -// File @zeppelin-solidity/contracts/access/IAccessControl.sol@v4.7.3 -// License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _remove(set._inner, value); + } + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { + return _contains(set._inner, value); + } -/** - * @dev External interface of AccessControl declared to support ERC165 detection. - */ -interface IAccessControl { /** - * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` + * @dev Returns the number of values in the set. O(1). + */ + function length(Bytes32Set storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). * - * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite - * {RoleAdminChanged} not being emitted signaling this. + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. * - * _Available since v3.1._ + * Requirements: + * + * - `index` must be strictly less than {length}. */ - event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); + function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { + return _at(set._inner, index); + } /** - * @dev Emitted when `account` is granted `role`. + * @dev Return the entire set in an array * - * `sender` is the account that originated the contract call, an admin role - * bearer except when using {AccessControl-_setupRole}. + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ - event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); + function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { + return _values(set._inner); + } + + // AddressSet + + struct AddressSet { + Set _inner; + } /** - * @dev Emitted when `account` is revoked `role`. + * @dev Add a value to a set. O(1). * - * `sender` is the account that originated the contract call: - * - if using `revokeRole`, it is the admin role bearer - * - if using `renounceRole`, it is the role bearer (i.e. `account`) + * Returns true if the value was added to the set, that is if it was not + * already present. */ - event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); + function add(AddressSet storage set, address value) internal returns (bool) { + return _add(set._inner, bytes32(uint256(uint160(value)))); + } /** - * @dev Returns `true` if `account` has been granted `role`. + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. */ - function hasRole(bytes32 role, address account) external view returns (bool); + function remove(AddressSet storage set, address value) internal returns (bool) { + return _remove(set._inner, bytes32(uint256(uint160(value)))); + } /** - * @dev Returns the admin role that controls `role`. See {grantRole} and - * {revokeRole}. - * - * To change a role's admin, use {AccessControl-_setRoleAdmin}. + * @dev Returns true if the value is in the set. O(1). */ - function getRoleAdmin(bytes32 role) external view returns (bytes32); + function contains(AddressSet storage set, address value) internal view returns (bool) { + return _contains(set._inner, bytes32(uint256(uint160(value)))); + } /** - * @dev Grants `role` to `account`. + * @dev Returns the number of values in the set. O(1). + */ + function length(AddressSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). * - * If `account` had not been already granted `role`, emits a {RoleGranted} - * event. + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. * * Requirements: * - * - the caller must have ``role``'s admin role. + * - `index` must be strictly less than {length}. */ - function grantRole(bytes32 role, address account) external; + function at(AddressSet storage set, uint256 index) internal view returns (address) { + return address(uint160(uint256(_at(set._inner, index)))); + } /** - * @dev Revokes `role` from `account`. - * - * If `account` had been granted `role`, emits a {RoleRevoked} event. + * @dev Return the entire set in an array * - * Requirements: + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(AddressSet storage set) internal view returns (address[] memory) { + bytes32[] memory store = _values(set._inner); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // UintSet + + struct UintSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). * - * - the caller must have ``role``'s admin role. + * Returns true if the value was added to the set, that is if it was not + * already present. */ - function revokeRole(bytes32 role, address account) external; + function add(UintSet storage set, uint256 value) internal returns (bool) { + return _add(set._inner, bytes32(value)); + } /** - * @dev Revokes `role` from the calling account. + * @dev Removes a value from a set. O(1). * - * Roles are often managed via {grantRole} and {revokeRole}: this function's - * purpose is to provide a mechanism for accounts to lose their privileges - * if they are compromised (such as when a trusted device is misplaced). + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(UintSet storage set, uint256 value) internal returns (bool) { + return _remove(set._inner, bytes32(value)); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(UintSet storage set, uint256 value) internal view returns (bool) { + return _contains(set._inner, bytes32(value)); + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function length(UintSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). * - * If the calling account had been granted `role`, emits a {RoleRevoked} - * event. + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. * * Requirements: * - * - the caller must be `account`. + * - `index` must be strictly less than {length}. */ - function renounceRole(bytes32 role, address account) external; + function at(UintSet storage set, uint256 index) internal view returns (uint256) { + return uint256(_at(set._inner, index)); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(UintSet storage set) internal view returns (uint256[] memory) { + bytes32[] memory store = _values(set._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } } -// File @zeppelin-solidity/contracts/access/IAccessControlEnumerable.sol@v4.7.3 +// File @zeppelin-solidity/contracts/access/AccessControlEnumerable.sol@v4.7.3 // License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol) +// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol) + + /** - * @dev External interface of AccessControlEnumerable declared to support ERC165 detection. + * @dev Extension of {AccessControl} that allows enumerating the members of each role. */ -interface IAccessControlEnumerable is IAccessControl { +abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl { + using EnumerableSet for EnumerableSet.AddressSet; + + mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers; + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId); + } + /** * @dev Returns one of the accounts that have `role`. `index` must be a * value between 0 and {getRoleMemberCount}, non-inclusive. @@ -615,986 +941,1037 @@ interface IAccessControlEnumerable is IAccessControl { * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] * for more information. */ - function getRoleMember(bytes32 role, uint256 index) external view returns (address); + function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) { + return _roleMembers[role].at(index); + } /** * @dev Returns the number of accounts that have `role`. Can be used * together with {getRoleMember} to enumerate all bearers of a role. */ - function getRoleMemberCount(bytes32 role) external view returns (uint256); -} - -// File @zeppelin-solidity/contracts/utils/Context.sol@v4.7.3 -// License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) - + function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) { + return _roleMembers[role].length(); + } -/** - * @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; + /** + * @dev Overload {_grantRole} to track enumerable memberships + */ + function _grantRole(bytes32 role, address account) internal virtual override { + super._grantRole(role, account); + _roleMembers[role].add(account); } - function _msgData() internal view virtual returns (bytes calldata) { - return msg.data; + /** + * @dev Overload {_revokeRole} to track enumerable memberships + */ + function _revokeRole(bytes32 role, address account) internal virtual override { + super._revokeRole(role, account); + _roleMembers[role].remove(account); } } -// File @zeppelin-solidity/contracts/utils/Strings.sol@v4.7.3 +// File @zeppelin-solidity/contracts/security/Pausable.sol@v4.7.3 // License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol) +// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol) /** - * @dev String operations. + * @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. */ -library Strings { - bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; - uint8 private constant _ADDRESS_LENGTH = 20; +abstract contract Pausable is Context { + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); /** - * @dev Converts a `uint256` to its ASCII `string` decimal representation. + * @dev Emitted when the pause is lifted by `account`. */ - function toString(uint256 value) internal pure returns (string memory) { - // Inspired by OraclizeAPI's implementation - MIT licence - // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + event Unpaused(address account); - if (value == 0) { - return "0"; - } - uint256 temp = value; - uint256 digits; - while (temp != 0) { - digits++; - temp /= 10; - } - bytes memory buffer = new bytes(digits); - while (value != 0) { - digits -= 1; - buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); - value /= 10; - } - return string(buffer); + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + constructor() { + _paused = false; } /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. */ - function toHexString(uint256 value) internal pure returns (string memory) { - if (value == 0) { - return "0x00"; - } - uint256 temp = value; - uint256 length = 0; - while (temp != 0) { - length++; - temp >>= 8; - } - return toHexString(value, length); + modifier whenNotPaused() { + _requireNotPaused(); + _; } /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. */ - function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { - bytes memory buffer = new bytes(2 * length + 2); - buffer[0] = "0"; - buffer[1] = "x"; - for (uint256 i = 2 * length + 1; i > 1; --i) { - buffer[i] = _HEX_SYMBOLS[value & 0xf]; - value >>= 4; - } - require(value == 0, "Strings: hex length insufficient"); - return string(buffer); + modifier whenPaused() { + _requirePaused(); + _; } /** - * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. + * @dev Returns true if the contract is paused, and false otherwise. */ - function toHexString(address addr) internal pure returns (string memory) { - return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); + function paused() public view virtual returns (bool) { + return _paused; } -} -// File @zeppelin-solidity/contracts/utils/introspection/IERC165.sol@v4.7.3 -// License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + /** + * @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 Interface of the ERC165 standard, as defined in the - * https://eips.ethereum.org/EIPS/eip-165[EIP]. - * - * Implementers can declare support of contract interfaces, which can then be - * queried by others ({ERC165Checker}). - * - * For an implementation, see {ERC165}. - */ -interface IERC165 { /** - * @dev Returns true if this contract implements the interface defined by - * `interfaceId`. See the corresponding - * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] - * to learn more about how these ids are created. + * @dev Triggers stopped state. * - * This function call must use less than 30 000 gas. + * Requirements: + * + * - The contract must not be paused. */ - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} - -// File @zeppelin-solidity/contracts/utils/introspection/ERC165.sol@v4.7.3 -// License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) - + function _pause() internal virtual whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } -/** - * @dev Implementation of the {IERC165} interface. - * - * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check - * for the additional interface id that will be supported. For example: - * - * ```solidity - * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); - * } - * ``` - * - * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. - */ -abstract contract ERC165 is IERC165 { /** - * @dev See {IERC165-supportsInterface}. + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC165).interfaceId; + function _unpause() internal virtual whenPaused { + _paused = false; + emit Unpaused(_msgSender()); } } -// File @zeppelin-solidity/contracts/access/AccessControl.sol@v4.7.3 +// File contracts/ln/base/LnAccessController.sol // License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (access/AccessControl.sol) - - - -/** - * @dev Contract module that allows children to implement role-based access - * control mechanisms. This is a lightweight version that doesn't allow enumerating role - * members except through off-chain means by accessing the contract event logs. Some - * applications may benefit from on-chain enumerability, for those cases see - * {AccessControlEnumerable}. - * - * Roles are referred to by their `bytes32` identifier. These should be exposed - * in the external API and be unique. The best way to achieve this is by - * using `public constant` hash digests: - * - * ``` - * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); - * ``` - * - * Roles can be used to represent a set of permissions. To restrict access to a - * function call, use {hasRole}: - * - * ``` - * function foo() public { - * require(hasRole(MY_ROLE, msg.sender)); - * ... - * } - * ``` - * - * Roles can be granted and revoked dynamically via the {grantRole} and - * {revokeRole} functions. Each role has an associated admin role, and only - * accounts that have a role's admin role can call {grantRole} and {revokeRole}. - * - * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means - * that only accounts with this role will be able to grant or revoke other - * roles. More complex role relationships can be created by using - * {_setRoleAdmin}. - * - * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to - * grant and revoke this role. Extra precautions should be taken to secure - * accounts that have been granted it. - */ -abstract contract AccessControl is Context, IAccessControl, ERC165 { - struct RoleData { - mapping(address => bool) members; - bytes32 adminRole; +/// @title LnAccessController +/// @notice LnAccessController is a contract to control the access permission +/// @dev See https://github.com/helix-bridge/contracts/tree/master/helix-contract +contract LnAccessController is AccessControlEnumerable, Pausable { + bytes32 public constant DAO_ADMIN_ROLE = keccak256("DAO_ADMIN_ROLE"); + bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); + + modifier onlyDao() { + require(hasRole(DAO_ADMIN_ROLE, msg.sender), "lpBridge:Bad dao role"); + _; } - mapping(bytes32 => RoleData) private _roles; + modifier onlyOperator() { + require(hasRole(OPERATOR_ROLE, msg.sender), "lpBridge:Bad operator role"); + _; + } + + function _initialize(address dao) internal { + _setRoleAdmin(OPERATOR_ROLE, DAO_ADMIN_ROLE); + _setRoleAdmin(DAO_ADMIN_ROLE, DAO_ADMIN_ROLE); + _setupRole(DAO_ADMIN_ROLE, dao); + _setupRole(OPERATOR_ROLE, msg.sender); + } + + function unpause() external onlyOperator { + _unpause(); + } + + function pause() external onlyOperator { + _pause(); + } +} + +// File @zeppelin-solidity/contracts/token/ERC20/IERC20.sol@v4.7.3 +// License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) - bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { /** - * @dev Modifier that checks that an account has a specific role. Reverts - * with a standardized message including the required role. - * - * The format of the revert reason is given by the following regular expression: - * - * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). * - * _Available since v4.1._ + * Note that `value` may be zero. */ - modifier onlyRole(bytes32 role) { - _checkRole(role); - _; - } + event Transfer(address indexed from, address indexed to, uint256 value); /** - * @dev See {IERC165-supportsInterface}. + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId); - } + event Approval(address indexed owner, address indexed spender, uint256 value); /** - * @dev Returns `true` if `account` has been granted `role`. + * @dev Returns the amount of tokens in existence. */ - function hasRole(bytes32 role, address account) public view virtual override returns (bool) { - return _roles[role].members[account]; - } + function totalSupply() external view returns (uint256); /** - * @dev Revert with a standard message if `_msgSender()` is missing `role`. - * Overriding this function changes the behavior of the {onlyRole} modifier. - * - * Format of the revert message is described in {_checkRole}. - * - * _Available since v4.6._ + * @dev Returns the amount of tokens owned by `account`. */ - function _checkRole(bytes32 role) internal view virtual { - _checkRole(role, _msgSender()); - } + function balanceOf(address account) external view returns (uint256); /** - * @dev Revert with a standard message if `account` is missing `role`. + * @dev Moves `amount` tokens from the caller's account to `to`. * - * The format of the revert reason is given by the following regular expression: + * Returns a boolean value indicating whether the operation succeeded. * - * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ + * Emits a {Transfer} event. */ - function _checkRole(bytes32 role, address account) internal view virtual { - if (!hasRole(role, account)) { - revert( - string( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(uint160(account), 20), - " is missing role ", - Strings.toHexString(uint256(role), 32) - ) - ) - ); - } - } + function transfer(address to, uint256 amount) external returns (bool); /** - * @dev Returns the admin role that controls `role`. See {grantRole} and - * {revokeRole}. + * @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. * - * To change a role's admin, use {_setRoleAdmin}. + * This value changes when {approve} or {transferFrom} are called. */ - function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) { - return _roles[role].adminRole; - } + function allowance(address owner, address spender) external view returns (uint256); /** - * @dev Grants `role` to `account`. - * - * If `account` had not been already granted `role`, emits a {RoleGranted} - * event. + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * - * Requirements: + * Returns a boolean value indicating whether the operation succeeded. * - * - the caller must have ``role``'s admin role. + * 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 * - * May emit a {RoleGranted} event. + * Emits an {Approval} event. */ - function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { - _grantRole(role, account); - } + function approve(address spender, uint256 amount) external returns (bool); /** - * @dev Revokes `role` from `account`. - * - * If `account` had been granted `role`, emits a {RoleRevoked} event. - * - * Requirements: + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. * - * - the caller must have ``role``'s admin role. + * Returns a boolean value indicating whether the operation succeeded. * - * May emit a {RoleRevoked} event. + * Emits a {Transfer} event. */ - function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { - _revokeRole(role, account); - } + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} - /** - * @dev Revokes `role` from the calling account. - * - * Roles are often managed via {grantRole} and {revokeRole}: this function's - * purpose is to provide a mechanism for accounts to lose their privileges - * if they are compromised (such as when a trusted device is misplaced). - * - * If the calling account had been revoked `role`, emits a {RoleRevoked} - * event. - * - * Requirements: - * - * - the caller must be `account`. - * - * May emit a {RoleRevoked} event. - */ - function renounceRole(bytes32 role, address account) public virtual override { - require(account == _msgSender(), "AccessControl: can only renounce roles for self"); +// File contracts/ln/base/LnBridgeHelper.sol +// License-Identifier: MIT - _revokeRole(role, account); +contract LnBridgeHelper { + bytes32 constant public INIT_SLASH_TRANSFER_ID = bytes32(uint256(1)); + + struct TransferParameter { + bytes32 previousTransferId; + address provider; + address sourceToken; + address targetToken; + uint112 amount; + uint64 timestamp; + address receiver; } - /** - * @dev Grants `role` to `account`. - * - * If `account` had not been already granted `role`, emits a {RoleGranted} - * event. Note that unlike {grantRole}, this function doesn't perform any - * checks on the calling account. - * - * May emit a {RoleGranted} event. - * - * [WARNING] - * ==== - * This function should only be called from the constructor when setting - * up the initial roles for the system. - * - * Using this function in any other way is effectively circumventing the admin - * system imposed by {AccessControl}. - * ==== - * - * NOTE: This function is deprecated in favor of {_grantRole}. - */ - function _setupRole(bytes32 role, address account) internal virtual { - _grantRole(role, account); + function _safeTransfer( + address token, + address receiver, + uint256 amount + ) internal { + (bool success, bytes memory data) = token.call(abi.encodeWithSelector( + IERC20.transfer.selector, + receiver, + amount + )); + require(success && (data.length == 0 || abi.decode(data, (bool))), "lnBridgeHelper:transfer token failed"); } - /** - * @dev Sets `adminRole` as ``role``'s admin role. - * - * Emits a {RoleAdminChanged} event. - */ - function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { - bytes32 previousAdminRole = getRoleAdmin(role); - _roles[role].adminRole = adminRole; - emit RoleAdminChanged(role, previousAdminRole, adminRole); + function _safeTransferFrom( + address token, + address sender, + address receiver, + uint256 amount + ) internal { + (bool success, bytes memory data) = token.call(abi.encodeWithSelector( + IERC20.transferFrom.selector, + sender, + receiver, + amount + )); + require(success && (data.length == 0 || abi.decode(data, (bool))), "lnBridgeHelper:transferFrom token failed"); } - /** - * @dev Grants `role` to `account`. - * - * Internal function without access restriction. - * - * May emit a {RoleGranted} event. - */ - function _grantRole(bytes32 role, address account) internal virtual { - if (!hasRole(role, account)) { - _roles[role].members[account] = true; - emit RoleGranted(role, account, _msgSender()); - } + function getProviderKey(address provider, address sourceToken) pure public returns(bytes32) { + return keccak256(abi.encodePacked( + provider, + sourceToken + )); + } + + function getDefaultProviderKey(address provider, address sourceToken, address targetToken) pure public returns(bytes32) { + return keccak256(abi.encodePacked( + provider, + sourceToken, + targetToken + )); } +} - /** - * @dev Revokes `role` from `account`. - * - * Internal function without access restriction. - * - * May emit a {RoleRevoked} event. - */ - function _revokeRole(bytes32 role, address account) internal virtual { - if (hasRole(role, account)) { - _roles[role].members[account] = false; - emit RoleRevoked(role, account, _msgSender()); - } - } +// File contracts/ln/interface/ILnDefaultBridgeTarget.sol +// License-Identifier: MIT + + +interface ILnDefaultBridgeTarget { + function slash( + LnBridgeHelper.TransferParameter memory params, + address slasher, + uint112 fee, + uint112 penalty + ) external; + + function withdraw( + bytes32 lastTransferId, + uint64 withdrawNonce, + address provider, + address sourceToken, + address targetToken, + uint112 amount + ) external; } -// File @zeppelin-solidity/contracts/utils/structs/EnumerableSet.sol@v4.7.3 +// File contracts/ln/base/LnDefaultBridgeSource.sol // License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (utils/structs/EnumerableSet.sol) -/** - * @dev Library for managing - * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive - * types. - * - * Sets have the following properties: - * - * - Elements are added, removed, and checked for existence in constant time - * (O(1)). - * - Elements are enumerated in O(n). No guarantees are made on the ordering. - * - * ``` - * contract Example { - * // Add the library methods - * using EnumerableSet for EnumerableSet.AddressSet; - * - * // Declare a set state variable - * EnumerableSet.AddressSet private mySet; - * } - * ``` - * - * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) - * and `uint256` (`UintSet`) are supported. - * - * [WARNING] - * ==== - * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure unusable. - * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. - * - * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an array of EnumerableSet. - * ==== - */ -library EnumerableSet { - // To implement this library for multiple types with as little code - // repetition as possible, we write it in terms of a generic Set type with - // bytes32 values. - // The Set implementation uses private functions, and user-facing - // implementations (such as AddressSet) are just wrappers around the - // underlying Set. - // This means that we can only create new EnumerableSets for types that fit - // in bytes32. - struct Set { - // Storage of set values - bytes32[] _values; - // Position of the value in the `values` array, plus 1 because index 0 - // means a value is not in the set. - mapping(bytes32 => uint256) _indexes; +/// @title LnPositiveBridgeSource +/// @notice LnPositiveBridgeSource is a contract to help user transfer token to liquidity node and generate proof, +/// then the liquidity node must transfer the same amount of the token to the user on target chain. +/// Otherwise if timeout the slasher can send a slash request message to target chain, then force transfer from lnProvider's margin to the user. +/// @dev See https://github.com/helix-bridge/contracts/tree/master/helix-contract +contract LnDefaultBridgeSource is LnBridgeHelper { + // the time(seconds) for liquidity provider to delivery message + // if timeout, slasher can work. + uint256 constant public MIN_SLASH_TIMESTAMP = 30 * 60; + // liquidity fee base rate + // liquidityFee = liquidityFeeRate / LIQUIDITY_FEE_RATE_BASE * sendAmount + uint256 constant public LIQUIDITY_FEE_RATE_BASE = 100000; + // max transfer amount one time + uint256 constant public MAX_TRANSFER_AMOUNT = type(uint112).max; + // the registered token info + // sourceToken and targetToken is the pair of erc20 token addresses + // if sourceToken == address(0), then it's native token + // if targetToken == address(0), then remote is native token + // * `protocolFee` is the protocol fee charged by system + // * `penaltyLnCollateral` is penalty from lnProvider when the transfer slashed, if we adjust this value, it'll not affect the old transfers. + struct TokenInfo { + address targetToken; + uint112 protocolFee; + uint112 penaltyLnCollateral; + uint8 sourceDecimals; + uint8 targetDecimals; + bool isRegistered; } - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function _add(Set storage set, bytes32 value) private returns (bool) { - if (!_contains(set, value)) { - set._values.push(value); - // The value is stored at length-1, but we add 1 to all indexes - // and use 0 as a sentinel value - set._indexes[value] = set._values.length; - return true; - } else { - return false; - } + // provider fee is paid to liquidity node's account + // the fee is charged by the same token that user transfered + // providerFee = baseFee + liquidityFeeRate/LIQUIDITY_FEE_RATE_BASE * sendAmount + struct LnProviderFee { + uint112 baseFee; + uint8 liquidityFeeRate; + } + + struct LnProviderInfo { + LnProviderFee fee; + // we use this nonce to generate the unique withdraw id + uint64 withdrawNonce; + bytes32 lastTransferId; + } + // the Snapshot is the state of the token bridge when user prepare to transfer across chains. + // If the snapshot updated when the across chain transfer confirmed, it will + // 1. if lastTransferId or withdrawNonce updated, revert + // 2. if totalFee increase, revert + // 3. if totalFee decrease, success + struct Snapshot { + address provider; + address sourceToken; + bytes32 transferId; + uint112 totalFee; + uint64 withdrawNonce; } - /** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ - function _remove(Set storage set, bytes32 value) private returns (bool) { - // We read and store the value's index to prevent multiple reads from the same storage slot - uint256 valueIndex = set._indexes[value]; - - if (valueIndex != 0) { - // Equivalent to contains(set, value) - // To delete an element from the _values array in O(1), we swap the element to delete with the last one in - // the array, and then remove the last element (sometimes called as 'swap and pop'). - // This modifies the order of the array, as noted in {at}. + // lock info + // the fee and penalty is the state of the transfer confirmed + struct LockInfo { + uint112 fee; + uint112 penalty; + bool isLocked; + } + // sourceToken => token info + mapping(address=>TokenInfo) public tokenInfos; + // providerKey => provider info + mapping(bytes32=>LnProviderInfo) public lnProviders; + // transferId => lock info + mapping(bytes32=>LockInfo) public lockInfos; - uint256 toDeleteIndex = valueIndex - 1; - uint256 lastIndex = set._values.length - 1; + address public protocolFeeReceiver; - if (lastIndex != toDeleteIndex) { - bytes32 lastValue = set._values[lastIndex]; + event TokenLocked( + bytes32 transferId, + address provider, + address sourceToken, + uint112 amount, + uint112 fee, + address receiver); + event LnProviderUpdated(address provider, address sourceToken, uint112 baseFee, uint8 liquidityfeeRate); - // Move the last value to the index where the value to delete is - set._values[toDeleteIndex] = lastValue; - // Update the index for the moved value - set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex - } + // protocolFeeReceiver is the protocol fee reciever, we don't use the contract itself as the receiver + function _setFeeReceiver(address _feeReceiver) internal { + require(_feeReceiver != address(this), "invalid system fee receiver"); + protocolFeeReceiver = _feeReceiver; + } - // Delete the slot where the moved value was stored - set._values.pop(); + // register or update token info, it can be only called by contract owner + // source token can only map a unique target token on target chain + function _setTokenInfo( + address _sourceToken, + address _targetToken, + uint112 _protocolFee, + uint112 _penaltyLnCollateral, + uint8 _sourceDecimals, + uint8 _targetDecimals + ) internal { + tokenInfos[_sourceToken] = TokenInfo( + _targetToken, + _protocolFee, + _penaltyLnCollateral, + _sourceDecimals, + _targetDecimals, + true + ); + } - // Delete the index for the deleted slot - delete set._indexes[value]; + // lnProvider register + // 1. set fee on source chain + // 2. deposit margin on target chain + function setProviderFee( + address sourceToken, + uint112 baseFee, + uint8 liquidityFeeRate + ) external { + TokenInfo memory tokenInfo = tokenInfos[sourceToken]; + require(tokenInfo.isRegistered, "token not registered"); + bytes32 providerKey = getDefaultProviderKey(msg.sender, sourceToken, tokenInfo.targetToken); + LnProviderFee memory providerFee = LnProviderFee(baseFee, liquidityFeeRate); - return true; - } else { - return false; - } - } + // we only update the field fee of the provider info + // if the provider has not been registered, then this line will register, otherwise update fee + lnProviders[providerKey].fee = providerFee; - /** - * @dev Returns true if the value is in the set. O(1). - */ - function _contains(Set storage set, bytes32 value) private view returns (bool) { - return set._indexes[value] != 0; + emit LnProviderUpdated(msg.sender, sourceToken, baseFee, liquidityFeeRate); } - /** - * @dev Returns the number of values on the set. O(1). - */ - function _length(Set storage set) private view returns (uint256) { - return set._values.length; + function calculateProviderFee(LnProviderFee memory fee, uint112 amount) internal pure returns(uint256) { + return uint256(fee.baseFee) + uint256(fee.liquidityFeeRate) * uint256(amount) / LIQUIDITY_FEE_RATE_BASE; } - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function _at(Set storage set, uint256 index) private view returns (bytes32) { - return set._values[index]; + // the fee user should paid when transfer. + // totalFee = providerFee + protocolFee + function totalFee(address provider, address sourceToken, uint112 amount) external view returns(uint256) { + TokenInfo memory tokenInfo = tokenInfos[sourceToken]; + bytes32 providerKey = getDefaultProviderKey(provider, sourceToken, tokenInfo.targetToken); + LnProviderInfo memory providerInfo = lnProviders[providerKey]; + uint256 providerFee = calculateProviderFee(providerInfo.fee, amount); + return providerFee + tokenInfo.protocolFee; } - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function _values(Set storage set) private view returns (bytes32[] memory) { - return set._values; - } + // This function transfers tokens from the user to LnProvider and generates a proof on the source chain. + // The snapshot represents the state of the LN bridge for this LnProvider, obtained by the off-chain indexer. + // If the chain state is updated and does not match the snapshot state, the transaction will be reverted. + // 1. the state(lastTransferId, fee, withdrawNonce) must match snapshot + // 2. transferId not exist + function transferAndLockMargin( + Snapshot calldata snapshot, + uint112 amount, + address receiver + ) external payable { + require(amount > 0, "invalid amount"); - // Bytes32Set + TokenInfo memory tokenInfo = tokenInfos[snapshot.sourceToken]; + require(tokenInfo.isRegistered, "token not registered"); + + bytes32 providerKey = getDefaultProviderKey(snapshot.provider, snapshot.sourceToken, tokenInfo.targetToken); - struct Bytes32Set { - Set _inner; - } + LnProviderInfo memory providerInfo = lnProviders[providerKey]; + uint256 providerFee = calculateProviderFee(providerInfo.fee, amount); - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { - return _add(set._inner, value); - } + // the chain state not match snapshot + require(providerInfo.lastTransferId == snapshot.transferId, "snapshot expired:transfer"); + require(snapshot.withdrawNonce == providerInfo.withdrawNonce, "snapshot expired:withdraw"); + require(snapshot.totalFee >= providerFee + tokenInfo.protocolFee && providerFee > 0, "fee is invalid"); + + uint112 targetAmount = _sourceAmountToTargetAmount(tokenInfo, uint256(amount)); + bytes32 transferId = keccak256(abi.encodePacked( + snapshot.transferId, + snapshot.provider, + snapshot.sourceToken, + tokenInfo.targetToken, + receiver, + uint64(block.timestamp), + targetAmount + )); + require(!lockInfos[transferId].isLocked, "transferId exist"); + // if the transfer refund, then the fee and penalty should be given to slasher, but the protocol fee is ignored + // and we use the penalty value configure at the moment transfer confirmed + lockInfos[transferId] = LockInfo(snapshot.totalFee, tokenInfo.penaltyLnCollateral, true); - /** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ - function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { - return _remove(set._inner, value); - } + // update the state to prevent other transfers using the same snapshot + lnProviders[providerKey].lastTransferId = transferId; - /** - * @dev Returns true if the value is in the set. O(1). - */ - function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { - return _contains(set._inner, value); + if (snapshot.sourceToken == address(0)) { + require(amount + snapshot.totalFee == msg.value, "amount unmatched"); + payable(snapshot.provider).transfer(amount + providerFee); + if (tokenInfo.protocolFee > 0) { + payable(protocolFeeReceiver).transfer(tokenInfo.protocolFee); + } + uint256 refund = snapshot.totalFee - tokenInfo.protocolFee - providerFee; + if ( refund > 0 ) { + payable(msg.sender).transfer(refund); + } + } else { + _safeTransferFrom( + snapshot.sourceToken, + msg.sender, + snapshot.provider, + amount + providerFee + ); + if (tokenInfo.protocolFee > 0) { + _safeTransferFrom( + snapshot.sourceToken, + msg.sender, + protocolFeeReceiver, + tokenInfo.protocolFee + ); + } + } + emit TokenLocked( + transferId, + snapshot.provider, + snapshot.sourceToken, + targetAmount, + uint112(providerFee), + receiver); } - /** - * @dev Returns the number of values in the set. O(1). - */ - function length(Bytes32Set storage set) internal view returns (uint256) { - return _length(set._inner); + function _sourceAmountToTargetAmount( + TokenInfo memory tokenInfo, + uint256 amount + ) internal pure returns(uint112) { + uint256 targetAmount = amount * 10**tokenInfo.targetDecimals / 10**tokenInfo.sourceDecimals; + require(targetAmount < MAX_TRANSFER_AMOUNT, "overflow amount"); + return uint112(targetAmount); } - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { - return _at(set._inner, index); - } + function _slashAndRemoteRelease( + TransferParameter memory params, + bytes32 expectedTransferId + ) internal view returns(bytes memory message) { + require(block.timestamp > params.timestamp + MIN_SLASH_TIMESTAMP, "invalid timestamp"); + TokenInfo memory tokenInfo = tokenInfos[params.sourceToken]; + require(tokenInfo.isRegistered, "token not registered"); + uint112 targetAmount = _sourceAmountToTargetAmount(tokenInfo, uint256(params.amount)); - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { - return _values(set._inner); + bytes32 transferId = keccak256(abi.encodePacked( + params.previousTransferId, + params.provider, + params.sourceToken, + params.targetToken, + params.receiver, + params.timestamp, + targetAmount + )); + require(expectedTransferId == transferId, "expected transfer id not match"); + LockInfo memory lockInfo = lockInfos[transferId]; + require(lockInfo.isLocked, "lock info not match"); + uint112 targetFee = _sourceAmountToTargetAmount(tokenInfo, lockInfo.fee); + uint112 targetPenalty = _sourceAmountToTargetAmount(tokenInfo, lockInfo.penalty); + + message = _encodeSlashCall( + params, + msg.sender, + targetFee, + targetPenalty + ); } - // AddressSet + function _withdrawMargin( + address sourceToken, + uint112 amount + ) internal returns(bytes memory message) { + TokenInfo memory tokenInfo = tokenInfos[sourceToken]; + require(tokenInfo.isRegistered, "token not registered"); - struct AddressSet { - Set _inner; + bytes32 providerKey = getDefaultProviderKey(msg.sender, sourceToken, tokenInfo.targetToken); + LnProviderInfo memory providerInfo = lnProviders[providerKey]; + lnProviders[providerKey].withdrawNonce = providerInfo.withdrawNonce + 1; + uint112 targetAmount = _sourceAmountToTargetAmount(tokenInfo, amount); + message = _encodeWithdrawCall( + providerInfo.lastTransferId, + providerInfo.withdrawNonce + 1, + msg.sender, + sourceToken, + tokenInfo.targetToken, + targetAmount + ); } - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function add(AddressSet storage set, address value) internal returns (bool) { - return _add(set._inner, bytes32(uint256(uint160(value)))); + function _encodeSlashCall( + TransferParameter memory params, + address slasher, + uint112 fee, + uint112 penalty + ) internal pure returns(bytes memory message) { + return abi.encodeWithSelector( + ILnDefaultBridgeTarget.slash.selector, + params, + slasher, + fee, + penalty + ); } - /** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ - function remove(AddressSet storage set, address value) internal returns (bool) { - return _remove(set._inner, bytes32(uint256(uint160(value)))); + function _encodeWithdrawCall( + bytes32 lastTransferId, + uint64 withdrawNonce, + address provider, + address sourceToken, + address targetToken, + uint112 amount + ) internal pure returns(bytes memory message) { + return abi.encodeWithSelector( + ILnDefaultBridgeTarget.withdraw.selector, + lastTransferId, + withdrawNonce, + provider, + sourceToken, + targetToken, + amount + ); } +} - /** - * @dev Returns true if the value is in the set. O(1). - */ - function contains(AddressSet storage set, address value) internal view returns (bool) { - return _contains(set._inner, bytes32(uint256(uint160(value)))); - } +// File @zeppelin-solidity/contracts/utils/Address.sol@v4.7.3 +// License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) - /** - * @dev Returns the number of values in the set. O(1). - */ - function length(AddressSet storage set) internal view returns (uint256) { - return _length(set._inner); - } +/** + * @dev Collection of functions related to the address type + */ +library Address { /** - * @dev Returns the value stored at position `index` in the set. O(1). + * @dev Returns true if `account` is a contract. * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. + * [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. * - * Requirements: + * Among others, `isContract` will return false for the following + * types of addresses: * - * - `index` must be strictly less than {length}. - */ - function at(AddressSet storage set, uint256 index) internal view returns (address) { - return address(uint160(uint256(_at(set._inner, index)))); - } - - /** - * @dev Return the entire set in an array + * - 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 + * ==== * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + * [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 values(AddressSet storage set) internal view returns (address[] memory) { - bytes32[] memory store = _values(set._inner); - address[] memory result; - - /// @solidity memory-safe-assembly - assembly { - result := store - } - - return result; - } - - // UintSet + 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. - struct UintSet { - Set _inner; + return account.code.length > 0; } /** - * @dev Add a value to a set. O(1). + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function add(UintSet storage set, uint256 value) internal returns (bool) { - return _add(set._inner, bytes32(value)); - } - - /** - * @dev Removes a value from a set. O(1). + * 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. * - * Returns true if the value was removed from the set, that is if it was - * present. + * 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 remove(UintSet storage set, uint256 value) internal returns (bool) { - return _remove(set._inner, bytes32(value)); + 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 Returns true if the value is in the set. O(1). + * @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 contains(UintSet storage set, uint256 value) internal view returns (bool) { - return _contains(set._inner, bytes32(value)); + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); } /** - * @dev Returns the number of values on the set. O(1). + * @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 length(UintSet storage set) internal view returns (uint256) { - return _length(set._inner); + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); } /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. * * Requirements: * - * - `index` must be strictly less than {length}. + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ */ - function at(UintSet storage set, uint256 index) internal view returns (uint256) { - return uint256(_at(set._inner, index)); + 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 Return the entire set in an array + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + * _Available since v3.1._ */ - function values(UintSet storage set) internal view returns (uint256[] memory) { - bytes32[] memory store = _values(set._inner); - uint256[] memory result; - - /// @solidity memory-safe-assembly - assembly { - result := store - } + 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"); - return result; + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); } -} - -// File @zeppelin-solidity/contracts/access/AccessControlEnumerable.sol@v4.7.3 -// License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol) - - - - -/** - * @dev Extension of {AccessControl} that allows enumerating the members of each role. - */ -abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl { - using EnumerableSet for EnumerableSet.AddressSet; - - mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers; /** - * @dev See {IERC165-supportsInterface}. + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId); + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); } /** - * @dev Returns one of the accounts that have `role`. `index` must be a - * value between 0 and {getRoleMemberCount}, non-inclusive. - * - * Role bearers are not sorted in any particular way, and their ordering may - * change at any point. + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. * - * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure - * you perform all queries on the same block. See the following - * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] - * for more information. + * _Available since v3.3._ */ - function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) { - return _roleMembers[role].at(index); + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); } /** - * @dev Returns the number of accounts that have `role`. Can be used - * together with {getRoleMember} to enumerate all bearers of a role. + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ */ - function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) { - return _roleMembers[role].length(); + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** - * @dev Overload {_grantRole} to track enumerable memberships + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ */ - function _grantRole(bytes32 role, address account) internal virtual override { - super._grantRole(role, account); - _roleMembers[role].add(account); + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); } /** - * @dev Overload {_revokeRole} to track enumerable memberships + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ */ - function _revokeRole(bytes32 role, address account) internal virtual override { - super._revokeRole(role, account); - _roleMembers[role].remove(account); + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } } } -// File @zeppelin-solidity/contracts/security/Pausable.sol@v4.7.3 +// File @zeppelin-solidity/contracts/proxy/utils/Initializable.sol@v4.7.3 // License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol) +// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol) /** - * @dev Contract module which allows children to implement an emergency stop - * mechanism that can be triggered by an authorized account. + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * - * 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. + * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be + * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in + * case an upgrade adds a module that needs to be initialized. + * + * For example: + * + * [.hljs-theme-light.nopadding] + * ``` + * contract MyToken is ERC20Upgradeable { + * function initialize() initializer public { + * __ERC20_init("MyToken", "MTK"); + * } + * } + * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + * function initializeV2() reinitializer(2) public { + * __ERC20Permit_init("MyToken"); + * } + * } + * ``` + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + * + * [CAUTION] + * ==== + * Avoid leaving a contract uninitialized. + * + * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation + * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke + * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: + * + * [.hljs-theme-light.nopadding] + * ``` + * /// @custom:oz-upgrades-unsafe-allow constructor + * constructor() { + * _disableInitializers(); + * } + * ``` + * ==== */ -abstract contract Pausable is Context { +abstract contract Initializable { /** - * @dev Emitted when the pause is triggered by `account`. + * @dev Indicates that the contract has been initialized. + * @custom:oz-retyped-from bool */ - event Paused(address account); + uint8 private _initialized; /** - * @dev Emitted when the pause is lifted by `account`. + * @dev Indicates that the contract is in the process of being initialized. */ - event Unpaused(address account); - - bool private _paused; + bool private _initializing; /** - * @dev Initializes the contract in unpaused state. + * @dev Triggered when the contract has been initialized or reinitialized. */ - constructor() { - _paused = false; - } + event Initialized(uint8 version); /** - * @dev Modifier to make a function callable only when the contract is not paused. - * - * Requirements: - * - * - The contract must not be paused. + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. */ - modifier whenNotPaused() { - _requireNotPaused(); + modifier initializer() { + bool isTopLevelCall = !_initializing; + require( + (isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1), + "Initializable: contract is already initialized" + ); + _initialized = 1; + if (isTopLevelCall) { + _initializing = true; + } _; + if (isTopLevelCall) { + _initializing = false; + emit Initialized(1); + } } /** - * @dev Modifier to make a function callable only when the contract is paused. + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. * - * Requirements: + * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original + * initialization step. This is essential to configure modules that are added through upgrades and that require + * initialization. * - * - The contract must be paused. + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. */ - modifier whenPaused() { - _requirePaused(); + modifier reinitializer(uint8 version) { + require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + _initialized = version; + _initializing = true; _; + _initializing = false; + emit Initialized(version); } /** - * @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. + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. */ - function _pause() internal virtual whenNotPaused { - _paused = true; - emit Paused(_msgSender()); + modifier onlyInitializing() { + require(_initializing, "Initializable: contract is not initializing"); + _; } /** - * @dev Returns to normal state. - * - * Requirements: - * - * - The contract must be paused. + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. */ - function _unpause() internal virtual whenPaused { - _paused = false; - emit Unpaused(_msgSender()); + function _disableInitializers() internal virtual { + require(!_initializing, "Initializable: contract is initializing"); + if (_initialized < type(uint8).max) { + _initialized = type(uint8).max; + emit Initialized(type(uint8).max); + } } } -// File contracts/ln/base/LnAccessController.sol -// License-Identifier: MIT - - -/// @title LnAccessController -/// @notice LnAccessController is a contract to control the access permission -/// @dev See https://github.com/helix-bridge/contracts/tree/master/helix-contract -contract LnAccessController is AccessControlEnumerable, Pausable { - bytes32 public constant DAO_ADMIN_ROLE = keccak256("DAO_ADMIN_ROLE"); - bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); - - modifier onlyDao() { - require(hasRole(DAO_ADMIN_ROLE, msg.sender), "lpBridge:Bad dao role"); - _; - } - - modifier onlyOperator() { - require(hasRole(OPERATOR_ROLE, msg.sender), "lpBridge:Bad operator role"); - _; - } +// File @arbitrum/nitro-contracts/src/bridge/IDelayedMessageProvider.sol@v1.0.1 +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE +// License-Identifier: BUSL-1.1 - function _initialize(address dao) internal { - _setRoleAdmin(OPERATOR_ROLE, DAO_ADMIN_ROLE); - _setRoleAdmin(DAO_ADMIN_ROLE, DAO_ADMIN_ROLE); - _setupRole(DAO_ADMIN_ROLE, dao); - _setupRole(OPERATOR_ROLE, msg.sender); - } +// solhint-disable-next-line compiler-version +pragma solidity >=0.6.9 <0.9.0; - function unpause() external onlyOperator { - _unpause(); - } +interface IDelayedMessageProvider { + /// @dev event emitted when a inbox message is added to the Bridge's delayed accumulator + event InboxMessageDelivered(uint256 indexed messageNum, bytes data); - function pause() external onlyOperator { - _pause(); - } + /// @dev event emitted when a inbox message is added to the Bridge's delayed accumulator + /// same as InboxMessageDelivered but the batch data is available in tx.input + event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum); } // File @arbitrum/nitro-contracts/src/bridge/IOwnable.sol@v1.0.1 @@ -1724,23 +2101,6 @@ interface IBridge { function initialize(IOwnable rollup_) external; } -// File @arbitrum/nitro-contracts/src/bridge/IDelayedMessageProvider.sol@v1.0.1 -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE -// License-Identifier: BUSL-1.1 - -// solhint-disable-next-line compiler-version -pragma solidity >=0.6.9 <0.9.0; - -interface IDelayedMessageProvider { - /// @dev event emitted when a inbox message is added to the Bridge's delayed accumulator - event InboxMessageDelivered(uint256 indexed messageNum, bytes data); - - /// @dev event emitted when a inbox message is added to the Bridge's delayed accumulator - /// same as InboxMessageDelivered but the batch data is available in tx.input - event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum); -} - // File @arbitrum/nitro-contracts/src/libraries/IGasRefunder.sol@v1.0.1 // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE @@ -1886,616 +2246,256 @@ interface ISequencerInbox is IDelayedMessageProvider { bytes32 messageDataHash ) external; - function inboxAccs(uint256 index) external view returns (bytes32); - - function batchCount() external view returns (uint256); - - function isValidKeysetHash(bytes32 ksHash) external view returns (bool); - - /// @notice the creation block is intended to still be available after a keyset is deleted - function getKeysetCreationBlock(bytes32 ksHash) external view returns (uint256); - - // ---------- BatchPoster functions ---------- - - function addSequencerL2BatchFromOrigin( - uint256 sequenceNumber, - bytes calldata data, - uint256 afterDelayedMessagesRead, - IGasRefunder gasRefunder - ) external; - - function addSequencerL2Batch( - uint256 sequenceNumber, - bytes calldata data, - uint256 afterDelayedMessagesRead, - IGasRefunder gasRefunder, - uint256 prevMessageCount, - uint256 newMessageCount - ) external; - - // ---------- onlyRollupOrOwner functions ---------- - - /** - * @notice Set max delay for sequencer inbox - * @param maxTimeVariation_ the maximum time variation parameters - */ - function setMaxTimeVariation(MaxTimeVariation memory maxTimeVariation_) external; - - /** - * @notice Updates whether an address is authorized to be a batch poster at the sequencer inbox - * @param addr the address - * @param isBatchPoster_ if the specified address should be authorized as a batch poster - */ - function setIsBatchPoster(address addr, bool isBatchPoster_) external; - - /** - * @notice Makes Data Availability Service keyset valid - * @param keysetBytes bytes of the serialized keyset - */ - function setValidKeyset(bytes calldata keysetBytes) external; - - /** - * @notice Invalidates a Data Availability Service keyset - * @param ksHash hash of the keyset - */ - function invalidateKeysetHash(bytes32 ksHash) external; - - // ---------- initializer ---------- - - function initialize(IBridge bridge_, MaxTimeVariation calldata maxTimeVariation_) external; -} - -// File @arbitrum/nitro-contracts/src/bridge/IInbox.sol@v1.0.1 -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE -// License-Identifier: BUSL-1.1 - -// solhint-disable-next-line compiler-version -pragma solidity >=0.6.9 <0.9.0; - - - -interface IInbox is IDelayedMessageProvider { - function bridge() external view returns (IBridge); - - function sequencerInbox() external view returns (ISequencerInbox); - - /** - * @notice Send a generic L2 message to the chain - * @dev This method is an optimization to avoid having to emit the entirety of the messageData in a log. Instead validators are expected to be able to parse the data from the transaction's input - * This method will be disabled upon L1 fork to prevent replay attacks on L2 - * @param messageData Data of the message being sent - */ - function sendL2MessageFromOrigin(bytes calldata messageData) external returns (uint256); - - /** - * @notice Send a generic L2 message to the chain - * @dev This method can be used to send any type of message that doesn't require L1 validation - * This method will be disabled upon L1 fork to prevent replay attacks on L2 - * @param messageData Data of the message being sent - */ - function sendL2Message(bytes calldata messageData) external returns (uint256); - - function sendL1FundedUnsignedTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - uint256 nonce, - address to, - bytes calldata data - ) external payable returns (uint256); - - function sendL1FundedContractTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - address to, - bytes calldata data - ) external payable returns (uint256); - - function sendUnsignedTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - uint256 nonce, - address to, - uint256 value, - bytes calldata data - ) external returns (uint256); - - function sendContractTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - address to, - uint256 value, - bytes calldata data - ) external returns (uint256); - - /** - * @dev This method can only be called upon L1 fork and will not alias the caller - * This method will revert if not called from origin - */ - function sendL1FundedUnsignedTransactionToFork( - uint256 gasLimit, - uint256 maxFeePerGas, - uint256 nonce, - address to, - bytes calldata data - ) external payable returns (uint256); - - /** - * @dev This method can only be called upon L1 fork and will not alias the caller - * This method will revert if not called from origin - */ - function sendUnsignedTransactionToFork( - uint256 gasLimit, - uint256 maxFeePerGas, - uint256 nonce, - address to, - uint256 value, - bytes calldata data - ) external returns (uint256); - - /** - * @notice Send a message to initiate L2 withdrawal - * @dev This method can only be called upon L1 fork and will not alias the caller - * This method will revert if not called from origin - */ - function sendWithdrawEthToFork( - uint256 gasLimit, - uint256 maxFeePerGas, - uint256 nonce, - uint256 value, - address withdrawTo - ) external returns (uint256); - - /** - * @notice Get the L1 fee for submitting a retryable - * @dev This fee can be paid by funds already in the L2 aliased address or by the current message value - * @dev This formula may change in the future, to future proof your code query this method instead of inlining!! - * @param dataLength The length of the retryable's calldata, in bytes - * @param baseFee The block basefee when the retryable is included in the chain, if 0 current block.basefee will be used - */ - function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee) - external - view - returns (uint256); - - /** - * @notice Deposit eth from L1 to L2 to address of the sender if sender is an EOA, and to its aliased address if the sender is a contract - * @dev This does not trigger the fallback function when receiving in the L2 side. - * Look into retryable tickets if you are interested in this functionality. - * @dev This function should not be called inside contract constructors - */ - function depositEth() external payable returns (uint256); - - /** - * @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts - * @dev all msg.value will deposited to callValueRefundAddress on L2 - * @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error - * @param to destination L2 contract address - * @param l2CallValue call value for retryable L2 message - * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee - * @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance - * @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled - * @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) - * @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) - * @param data ABI encoded data of L2 message - * @return unique message number of the retryable transaction - */ - function createRetryableTicket( - address to, - uint256 l2CallValue, - uint256 maxSubmissionCost, - address excessFeeRefundAddress, - address callValueRefundAddress, - uint256 gasLimit, - uint256 maxFeePerGas, - bytes calldata data - ) external payable returns (uint256); - - /** - * @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts - * @dev Same as createRetryableTicket, but does not guarantee that submission will succeed by requiring the needed funds - * come from the deposit alone, rather than falling back on the user's L2 balance - * @dev Advanced usage only (does not rewrite aliases for excessFeeRefundAddress and callValueRefundAddress). - * createRetryableTicket method is the recommended standard. - * @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error - * @param to destination L2 contract address - * @param l2CallValue call value for retryable L2 message - * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee - * @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance - * @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled - * @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) - * @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) - * @param data ABI encoded data of L2 message - * @return unique message number of the retryable transaction - */ - function unsafeCreateRetryableTicket( - address to, - uint256 l2CallValue, - uint256 maxSubmissionCost, - address excessFeeRefundAddress, - address callValueRefundAddress, - uint256 gasLimit, - uint256 maxFeePerGas, - bytes calldata data - ) external payable returns (uint256); - - // ---------- onlyRollupOrOwner functions ---------- - - /// @notice pauses all inbox functionality - function pause() external; - - /// @notice unpauses all inbox functionality - function unpause() external; + function inboxAccs(uint256 index) external view returns (bytes32); - // ---------- initializer ---------- + function batchCount() external view returns (uint256); - /** - * @dev function to be called one time during the inbox upgrade process - * this is used to fix the storage slots - */ - function postUpgradeInit(IBridge _bridge) external; + function isValidKeysetHash(bytes32 ksHash) external view returns (bool); - function initialize(IBridge _bridge, ISequencerInbox _sequencerInbox) external; -} + /// @notice the creation block is intended to still be available after a keyset is deleted + function getKeysetCreationBlock(bytes32 ksHash) external view returns (uint256); -// File @zeppelin-solidity/contracts/utils/Address.sol@v4.7.3 -// License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) + // ---------- BatchPoster functions ---------- + function addSequencerL2BatchFromOrigin( + uint256 sequenceNumber, + bytes calldata data, + uint256 afterDelayedMessagesRead, + IGasRefunder gasRefunder + ) external; -/** - * @dev Collection of functions related to the address type - */ -library Address { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - * - * [IMPORTANT] - * ==== - * You shouldn't rely on `isContract` to protect against flash loan attacks! - * - * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets - * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract - * constructor. - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize/address.code.length, which returns 0 - // for contracts in construction, since the code is only stored at the end - // of the constructor execution. + function addSequencerL2Batch( + uint256 sequenceNumber, + bytes calldata data, + uint256 afterDelayedMessagesRead, + IGasRefunder gasRefunder, + uint256 prevMessageCount, + uint256 newMessageCount + ) external; - return account.code.length > 0; - } + // ---------- onlyRollupOrOwner functions ---------- /** - * @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]. + * @notice Set max delay for sequencer inbox + * @param maxTimeVariation_ the maximum time variation parameters */ - 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"); - } + function setMaxTimeVariation(MaxTimeVariation memory maxTimeVariation_) external; /** - * @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._ + * @notice Updates whether an address is authorized to be a batch poster at the sequencer inbox + * @param addr the address + * @param isBatchPoster_ if the specified address should be authorized as a batch poster */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCall(target, data, "Address: low-level call failed"); - } + function setIsBatchPoster(address addr, bool isBatchPoster_) external; /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ + * @notice Makes Data Availability Service keyset valid + * @param keysetBytes bytes of the serialized keyset */ - function functionCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); - } + function setValidKeyset(bytes calldata keysetBytes) external; /** - * @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._ + * @notice Invalidates a Data Availability Service keyset + * @param ksHash hash of the keyset */ - 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"); - } + function invalidateKeysetHash(bytes32 ksHash) external; - /** - * @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"); + // ---------- initializer ---------- - (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResult(success, returndata, errorMessage); - } + function initialize(IBridge bridge_, MaxTimeVariation calldata maxTimeVariation_) external; +} - /** - * @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"); - } +// File @arbitrum/nitro-contracts/src/bridge/IInbox.sol@v1.0.1 +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE +// License-Identifier: BUSL-1.1 + +// solhint-disable-next-line compiler-version +pragma solidity >=0.6.9 <0.9.0; - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall( - address target, - bytes memory data, - string memory errorMessage - ) internal view returns (bytes memory) { - require(isContract(target), "Address: static call to non-contract"); - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResult(success, returndata, errorMessage); - } + +interface IInbox is IDelayedMessageProvider { + function bridge() external view returns (IBridge); + + function sequencerInbox() external view returns (ISequencerInbox); /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ + * @notice Send a generic L2 message to the chain + * @dev This method is an optimization to avoid having to emit the entirety of the messageData in a log. Instead validators are expected to be able to parse the data from the transaction's input + * This method will be disabled upon L1 fork to prevent replay attacks on L2 + * @param messageData Data of the message being sent */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, "Address: low-level delegate call failed"); - } + function sendL2MessageFromOrigin(bytes calldata messageData) external returns (uint256); /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ + * @notice Send a generic L2 message to the chain + * @dev This method can be used to send any type of message that doesn't require L1 validation + * This method will be disabled upon L1 fork to prevent replay attacks on L2 + * @param messageData Data of the message being sent */ - function functionDelegateCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - require(isContract(target), "Address: delegate call to non-contract"); + function sendL2Message(bytes calldata messageData) external returns (uint256); + + function sendL1FundedUnsignedTransaction( + uint256 gasLimit, + uint256 maxFeePerGas, + uint256 nonce, + address to, + bytes calldata data + ) external payable returns (uint256); + + function sendL1FundedContractTransaction( + uint256 gasLimit, + uint256 maxFeePerGas, + address to, + bytes calldata data + ) external payable returns (uint256); + + function sendUnsignedTransaction( + uint256 gasLimit, + uint256 maxFeePerGas, + uint256 nonce, + address to, + uint256 value, + bytes calldata data + ) external returns (uint256); - (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResult(success, returndata, errorMessage); - } + function sendContractTransaction( + uint256 gasLimit, + uint256 maxFeePerGas, + address to, + uint256 value, + bytes calldata data + ) external returns (uint256); /** - * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason using the provided one. - * - * _Available since v4.3._ + * @dev This method can only be called upon L1 fork and will not alias the caller + * This method will revert if not called from origin */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { - if (success) { - return returndata; - } else { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } - } -} - -// File @zeppelin-solidity/contracts/proxy/utils/Initializable.sol@v4.7.3 -// License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol) - + function sendL1FundedUnsignedTransactionToFork( + uint256 gasLimit, + uint256 maxFeePerGas, + uint256 nonce, + address to, + bytes calldata data + ) external payable returns (uint256); -/** - * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed - * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an - * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer - * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. - * - * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be - * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in - * case an upgrade adds a module that needs to be initialized. - * - * For example: - * - * [.hljs-theme-light.nopadding] - * ``` - * contract MyToken is ERC20Upgradeable { - * function initialize() initializer public { - * __ERC20_init("MyToken", "MTK"); - * } - * } - * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { - * function initializeV2() reinitializer(2) public { - * __ERC20Permit_init("MyToken"); - * } - * } - * ``` - * - * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as - * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. - * - * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure - * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. - * - * [CAUTION] - * ==== - * Avoid leaving a contract uninitialized. - * - * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation - * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke - * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: - * - * [.hljs-theme-light.nopadding] - * ``` - * /// @custom:oz-upgrades-unsafe-allow constructor - * constructor() { - * _disableInitializers(); - * } - * ``` - * ==== - */ -abstract contract Initializable { /** - * @dev Indicates that the contract has been initialized. - * @custom:oz-retyped-from bool + * @dev This method can only be called upon L1 fork and will not alias the caller + * This method will revert if not called from origin */ - uint8 private _initialized; + function sendUnsignedTransactionToFork( + uint256 gasLimit, + uint256 maxFeePerGas, + uint256 nonce, + address to, + uint256 value, + bytes calldata data + ) external returns (uint256); /** - * @dev Indicates that the contract is in the process of being initialized. + * @notice Send a message to initiate L2 withdrawal + * @dev This method can only be called upon L1 fork and will not alias the caller + * This method will revert if not called from origin */ - bool private _initializing; + function sendWithdrawEthToFork( + uint256 gasLimit, + uint256 maxFeePerGas, + uint256 nonce, + uint256 value, + address withdrawTo + ) external returns (uint256); /** - * @dev Triggered when the contract has been initialized or reinitialized. + * @notice Get the L1 fee for submitting a retryable + * @dev This fee can be paid by funds already in the L2 aliased address or by the current message value + * @dev This formula may change in the future, to future proof your code query this method instead of inlining!! + * @param dataLength The length of the retryable's calldata, in bytes + * @param baseFee The block basefee when the retryable is included in the chain, if 0 current block.basefee will be used */ - event Initialized(uint8 version); + function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee) + external + view + returns (uint256); /** - * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, - * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. + * @notice Deposit eth from L1 to L2 to address of the sender if sender is an EOA, and to its aliased address if the sender is a contract + * @dev This does not trigger the fallback function when receiving in the L2 side. + * Look into retryable tickets if you are interested in this functionality. + * @dev This function should not be called inside contract constructors */ - modifier initializer() { - bool isTopLevelCall = !_initializing; - require( - (isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1), - "Initializable: contract is already initialized" - ); - _initialized = 1; - if (isTopLevelCall) { - _initializing = true; - } - _; - if (isTopLevelCall) { - _initializing = false; - emit Initialized(1); - } - } + function depositEth() external payable returns (uint256); /** - * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the - * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be - * used to initialize parent contracts. - * - * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original - * initialization step. This is essential to configure modules that are added through upgrades and that require - * initialization. - * - * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in - * a contract, executing them in the right order is up to the developer or operator. + * @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts + * @dev all msg.value will deposited to callValueRefundAddress on L2 + * @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error + * @param to destination L2 contract address + * @param l2CallValue call value for retryable L2 message + * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee + * @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance + * @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled + * @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) + * @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) + * @param data ABI encoded data of L2 message + * @return unique message number of the retryable transaction */ - modifier reinitializer(uint8 version) { - require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); - _initialized = version; - _initializing = true; - _; - _initializing = false; - emit Initialized(version); - } + function createRetryableTicket( + address to, + uint256 l2CallValue, + uint256 maxSubmissionCost, + address excessFeeRefundAddress, + address callValueRefundAddress, + uint256 gasLimit, + uint256 maxFeePerGas, + bytes calldata data + ) external payable returns (uint256); /** - * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the - * {initializer} and {reinitializer} modifiers, directly or indirectly. + * @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts + * @dev Same as createRetryableTicket, but does not guarantee that submission will succeed by requiring the needed funds + * come from the deposit alone, rather than falling back on the user's L2 balance + * @dev Advanced usage only (does not rewrite aliases for excessFeeRefundAddress and callValueRefundAddress). + * createRetryableTicket method is the recommended standard. + * @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error + * @param to destination L2 contract address + * @param l2CallValue call value for retryable L2 message + * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee + * @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance + * @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled + * @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) + * @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) + * @param data ABI encoded data of L2 message + * @return unique message number of the retryable transaction */ - modifier onlyInitializing() { - require(_initializing, "Initializable: contract is not initializing"); - _; - } + function unsafeCreateRetryableTicket( + address to, + uint256 l2CallValue, + uint256 maxSubmissionCost, + address excessFeeRefundAddress, + address callValueRefundAddress, + uint256 gasLimit, + uint256 maxFeePerGas, + bytes calldata data + ) external payable returns (uint256); + + // ---------- onlyRollupOrOwner functions ---------- + + /// @notice pauses all inbox functionality + function pause() external; + + /// @notice unpauses all inbox functionality + function unpause() external; + + // ---------- initializer ---------- /** - * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. - * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized - * to any version. It is recommended to use this to lock implementation contracts that are designed to be called - * through proxies. + * @dev function to be called one time during the inbox upgrade process + * this is used to fix the storage slots */ - function _disableInitializers() internal virtual { - require(!_initializing, "Initializable: contract is initializing"); - if (_initialized < type(uint8).max) { - _initialized = type(uint8).max; - emit Initialized(type(uint8).max); - } - } + function postUpgradeInit(IBridge _bridge) external; + + function initialize(IBridge _bridge, ISequencerInbox _sequencerInbox) external; } // File contracts/ln/Eth2ArbSource.sol diff --git a/helix-contract/flatten/lnv2/Eth2ArbTarget.sol b/helix-contract/flatten/lnv2/Eth2ArbTarget.sol index 6a2d2c88..115c7709 100644 --- a/helix-contract/flatten/lnv2/Eth2ArbTarget.sol +++ b/helix-contract/flatten/lnv2/Eth2ArbTarget.sol @@ -14,7 +14,7 @@ * '----------------' '----------------' '----------------' '----------------' '----------------' ' * * - * 7/31/2023 + * 8/7/2023 **/ pragma solidity ^0.8.10; @@ -44,6 +44,110 @@ abstract contract Context { } } +// File @zeppelin-solidity/contracts/security/Pausable.sol@v4.7.3 +// License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.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()); + } +} + // File @zeppelin-solidity/contracts/access/IAccessControl.sol@v4.7.3 // License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) @@ -970,110 +1074,6 @@ abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessCon } } -// File @zeppelin-solidity/contracts/security/Pausable.sol@v4.7.3 -// License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.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()); - } -} - // File contracts/ln/base/LnAccessController.sol // License-Identifier: MIT