From 47c733efd2eec232ab25ae55d6f126aa9dd7208b Mon Sep 17 00:00:00 2001 From: William Schwab <31592931+wschwab@users.noreply.github.com> Date: Mon, 15 May 2023 20:37:25 +0300 Subject: [PATCH 1/6] feat: implement mintable token predicate contracts (#234) (#236) * build(deps-dev): bump @types/node from 20.1.1 to 20.1.3 Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.1.1 to 20.1.3. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * feat: implement mintable token predicate contracts (#234) * feat: add ERC20 mintable predicates * test: add tests for root mintable ERC20 predicate * fix: mintable root erc20 predicate fixes * test: implement tests for child mintable erc20 predicate * feat: implement root mintable ERC721 predicate * feat: implement child mintable erc721 predicate * test: implement child mintable erc721 tests * test: implement root mintable erc721 predicate tests * feat: implement child mintable erc1155 predicate * feat: implement root mintable erc1155 predicate * test: implement root mintable erc1155 predicate tests * chore: minor doc fixes, fix slither * build(deps-dev): bump @openzeppelin/hardhat-upgrades Bumps [@openzeppelin/hardhat-upgrades](https://github.com/OpenZeppelin/openzeppelin-upgrades) from 1.25.0 to 1.26.0. - [Release notes](https://github.com/OpenZeppelin/openzeppelin-upgrades/releases) - [Commits](https://github.com/OpenZeppelin/openzeppelin-upgrades/compare/@openzeppelin/hardhat-upgrades@1.25.0...@openzeppelin/hardhat-upgrades@1.26.0) --- updated-dependencies: - dependency-name: "@openzeppelin/hardhat-upgrades" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: qedk <1994constant@gmail.com> Co-authored-by: Daniel Gretzke --- .../child/RootMintableERC1155Predicate.sol | 202 ++++++ .../child/RootMintableERC20Predicate.sol | 175 +++++ .../child/RootMintableERC721Predicate.sol | 198 ++++++ .../interfaces/child/IChildERC20Predicate.sol | 2 +- .../child/IRootMintableERC1155Predicate.sol | 86 +++ .../child/IRootMintableERC20Predicate.sol | 45 ++ .../child/IRootMintableERC721Predicate.sol | 71 ++ .../root/IChildMintableERC1155Predicate.sol | 52 ++ .../root/IChildMintableERC20Predicate.sol | 34 + .../root/IChildMintableERC721Predicate.sol | 52 ++ .../interfaces/root/IRootERC1155Predicate.sol | 3 +- .../interfaces/root/IRootERC20Predicate.sol | 3 +- .../interfaces/root/IRootERC721Predicate.sol | 10 +- .../root/ChildMintableERC1155Predicate.sol | 308 +++++++++ .../root/ChildMintableERC20Predicate.sol | 192 ++++++ .../root/ChildMintableERC721Predicate.sol | 285 ++++++++ contracts/root/RootERC1155Predicate.sol | 3 +- contracts/root/RootERC20Predicate.sol | 3 +- contracts/root/RootERC721Predicate.sol | 3 +- docs/child/RootMintableERC1155Predicate.md | 499 ++++++++++++++ docs/child/RootMintableERC20Predicate.md | 498 ++++++++++++++ docs/child/RootMintableERC721Predicate.md | 615 ++++++++++++++++++ .../child/IRootMintableERC1155Predicate.md | 215 ++++++ .../child/IRootMintableERC20Predicate.md | 150 +++++ .../child/IRootMintableERC721Predicate.md | 208 ++++++ .../root/IChildMintableERC1155Predicate.md | 193 ++++++ .../root/IChildMintableERC20Predicate.md | 147 +++++ .../root/IChildMintableERC721Predicate.md | 205 ++++++ docs/interfaces/root/IRootERC1155Predicate.md | 18 + docs/interfaces/root/IRootERC20Predicate.md | 18 + docs/interfaces/root/IRootERC721Predicate.md | 18 + docs/root/ChildMintableERC1155Predicate.md | 403 ++++++++++++ docs/root/ChildMintableERC20Predicate.md | 304 +++++++++ docs/root/ChildMintableERC721Predicate.md | 396 +++++++++++ package-lock.json | 106 ++- package.json | 4 +- .../RootMintableERC1155Predicate.test.ts | 361 ++++++++++ test/child/RootMintableERC20Predicate.test.ts | 316 +++++++++ .../child/RootMintableERC721Predicate.test.ts | 341 ++++++++++ .../ChildMintableERC1155Predicate.test.ts | 495 ++++++++++++++ test/root/ChildMintableERC20Predicate.test.ts | 386 +++++++++++ .../root/ChildMintableERC721Predicate.test.ts | 487 ++++++++++++++ 42 files changed, 8025 insertions(+), 85 deletions(-) create mode 100644 contracts/child/RootMintableERC1155Predicate.sol create mode 100644 contracts/child/RootMintableERC20Predicate.sol create mode 100644 contracts/child/RootMintableERC721Predicate.sol create mode 100644 contracts/interfaces/child/IRootMintableERC1155Predicate.sol create mode 100644 contracts/interfaces/child/IRootMintableERC20Predicate.sol create mode 100644 contracts/interfaces/child/IRootMintableERC721Predicate.sol create mode 100644 contracts/interfaces/root/IChildMintableERC1155Predicate.sol create mode 100644 contracts/interfaces/root/IChildMintableERC20Predicate.sol create mode 100644 contracts/interfaces/root/IChildMintableERC721Predicate.sol create mode 100644 contracts/root/ChildMintableERC1155Predicate.sol create mode 100644 contracts/root/ChildMintableERC20Predicate.sol create mode 100644 contracts/root/ChildMintableERC721Predicate.sol create mode 100644 docs/child/RootMintableERC1155Predicate.md create mode 100644 docs/child/RootMintableERC20Predicate.md create mode 100644 docs/child/RootMintableERC721Predicate.md create mode 100644 docs/interfaces/child/IRootMintableERC1155Predicate.md create mode 100644 docs/interfaces/child/IRootMintableERC20Predicate.md create mode 100644 docs/interfaces/child/IRootMintableERC721Predicate.md create mode 100644 docs/interfaces/root/IChildMintableERC1155Predicate.md create mode 100644 docs/interfaces/root/IChildMintableERC20Predicate.md create mode 100644 docs/interfaces/root/IChildMintableERC721Predicate.md create mode 100644 docs/root/ChildMintableERC1155Predicate.md create mode 100644 docs/root/ChildMintableERC20Predicate.md create mode 100644 docs/root/ChildMintableERC721Predicate.md create mode 100644 test/child/RootMintableERC1155Predicate.test.ts create mode 100644 test/child/RootMintableERC20Predicate.test.ts create mode 100644 test/child/RootMintableERC721Predicate.test.ts create mode 100644 test/root/ChildMintableERC1155Predicate.test.ts create mode 100644 test/root/ChildMintableERC20Predicate.test.ts create mode 100644 test/root/ChildMintableERC721Predicate.test.ts diff --git a/contracts/child/RootMintableERC1155Predicate.sol b/contracts/child/RootMintableERC1155Predicate.sol new file mode 100644 index 00000000..e1a203c8 --- /dev/null +++ b/contracts/child/RootMintableERC1155Predicate.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "../interfaces/child/IRootMintableERC1155Predicate.sol"; +import "../interfaces/IStateSender.sol"; + +// solhint-disable reason-string +contract RootMintableERC1155Predicate is Initializable, ERC1155Holder, IRootMintableERC1155Predicate { + IStateSender public l2StateSender; + address public stateReceiver; + address public childERC1155Predicate; + address public childTokenTemplate; + bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); + bytes32 public constant DEPOSIT_BATCH_SIG = keccak256("DEPOSIT_BATCH"); + bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW"); + bytes32 public constant WITHDRAW_BATCH_SIG = keccak256("WITHDRAW_BATCH"); + bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); + mapping(address => address) public rootTokenToChildToken; + + /** + * @notice Initilization function for RootMintableERC1155Predicate + * @param newL2StateSender Address of L2StateSender to send deposit information to + * @param newStateReceiver Address of StateReceiver to receive withdrawal information from + * @param newChildERC1155Predicate Address of child ERC1155 predicate to communicate with + * @dev Can only be called once. + */ + function initialize( + address newL2StateSender, + address newStateReceiver, + address newChildERC1155Predicate, + address newChildTokenTemplate + ) external initializer { + require( + newL2StateSender != address(0) && + newStateReceiver != address(0) && + newChildERC1155Predicate != address(0) && + newChildTokenTemplate != address(0), + "RootMintableERC1155Predicate: BAD_INITIALIZATION" + ); + l2StateSender = IStateSender(newL2StateSender); + stateReceiver = newStateReceiver; + childERC1155Predicate = newChildERC1155Predicate; + childTokenTemplate = newChildTokenTemplate; + } + + /** + * @inheritdoc IStateReceiver + * @notice Function to be used for token withdrawals + * @dev Can be extended to include other signatures for more functionality + */ + function onStateReceive(uint256 /* id */, address sender, bytes calldata data) external { + require(msg.sender == stateReceiver, "RootMintableERC1155Predicate: ONLY_STATE_RECEIVER"); + require(sender == childERC1155Predicate, "RootMintableERC1155Predicate: ONLY_CHILD_PREDICATE"); + + if (bytes32(data[:32]) == WITHDRAW_SIG) { + _withdraw(data[32:]); + } else if (bytes32(data[:32]) == WITHDRAW_BATCH_SIG) { + _withdrawBatch(data); + } else { + revert("RootMintableERC1155Predicate: INVALID_SIGNATURE"); + } + } + + /** + * @inheritdoc IRootMintableERC1155Predicate + */ + function deposit(IERC1155MetadataURI rootToken, uint256 tokenId, uint256 amount) external { + _deposit(rootToken, msg.sender, tokenId, amount); + } + + /** + * @inheritdoc IRootMintableERC1155Predicate + */ + function depositTo(IERC1155MetadataURI rootToken, address receiver, uint256 tokenId, uint256 amount) external { + _deposit(rootToken, receiver, tokenId, amount); + } + + /** + * @inheritdoc IRootMintableERC1155Predicate + */ + function depositBatch( + IERC1155MetadataURI rootToken, + address[] calldata receivers, + uint256[] calldata tokenIds, + uint256[] calldata amounts + ) external { + require( + receivers.length == tokenIds.length && receivers.length == amounts.length, + "RootMintableERC1155Predicate: INVALID_LENGTH" + ); + _depositBatch(rootToken, receivers, tokenIds, amounts); + } + + /** + * @inheritdoc IRootMintableERC1155Predicate + */ + function mapToken(IERC1155MetadataURI rootToken) public returns (address childToken) { + require(address(rootToken) != address(0), "RootMintableERC1155Predicate: INVALID_TOKEN"); + require( + rootTokenToChildToken[address(rootToken)] == address(0), + "RootMintableERC1155Predicate: ALREADY_MAPPED" + ); + + childToken = Clones.predictDeterministicAddress( + childTokenTemplate, + keccak256(abi.encodePacked(rootToken)), + childERC1155Predicate + ); + + rootTokenToChildToken[address(rootToken)] = childToken; + + string memory uri = ""; + // slither does not deal well with try-catch: https://github.com/crytic/slither/issues/982 + // slither-disable-next-line uninitialized-local,unused-return,variable-scope + try rootToken.uri(0) returns (string memory tokenUri) { + uri = tokenUri; + } catch {} + + l2StateSender.syncState(childERC1155Predicate, abi.encode(MAP_TOKEN_SIG, rootToken, uri)); + // slither-disable-next-line reentrancy-events + emit L2MintableTokenMapped(address(rootToken), childToken); + } + + function _deposit(IERC1155MetadataURI rootToken, address receiver, uint256 tokenId, uint256 amount) private { + address childToken = _getChildToken(rootToken); + + rootToken.safeTransferFrom(msg.sender, address(this), tokenId, amount, ""); + + l2StateSender.syncState( + childERC1155Predicate, + abi.encode(DEPOSIT_SIG, rootToken, msg.sender, receiver, tokenId, amount) + ); + // slither-disable-next-line reentrancy-events + emit L2MintableERC1155Deposit(address(rootToken), childToken, msg.sender, receiver, tokenId, amount); + } + + function _depositBatch( + IERC1155MetadataURI rootToken, + address[] calldata receivers, + uint256[] calldata tokenIds, + uint256[] calldata amounts + ) private { + address childToken = _getChildToken(rootToken); + + for (uint256 i = 0; i < tokenIds.length; ) { + rootToken.safeTransferFrom(msg.sender, address(this), tokenIds[i], amounts[i], ""); + unchecked { + ++i; + } + } + + l2StateSender.syncState( + childERC1155Predicate, + abi.encode(DEPOSIT_BATCH_SIG, rootToken, msg.sender, receivers, tokenIds, amounts) + ); + // slither-disable-next-line reentrancy-events + emit L2MintableERC1155DepositBatch(address(rootToken), childToken, msg.sender, receivers, tokenIds, amounts); + } + + function _withdraw(bytes calldata data) private { + (address rootToken, address withdrawer, address receiver, uint256 tokenId, uint256 amount) = abi.decode( + data, + (address, address, address, uint256, uint256) + ); + address childToken = rootTokenToChildToken[rootToken]; + assert(childToken != address(0)); // invariant because child predicate should have already mapped tokens + + IERC1155MetadataURI(rootToken).safeTransferFrom(address(this), receiver, tokenId, amount, ""); + // slither-disable-next-line reentrancy-events + emit L2MintableERC1155Withdraw(address(rootToken), childToken, withdrawer, receiver, tokenId, amount); + } + + function _withdrawBatch(bytes calldata data) private { + ( + , + address rootToken, + address withdrawer, + address[] memory receivers, + uint256[] memory tokenIds, + uint256[] memory amounts + ) = abi.decode(data, (bytes32, address, address, address[], uint256[], uint256[])); + address childToken = rootTokenToChildToken[rootToken]; + assert(childToken != address(0)); // invariant because child predicate should have already mapped tokens + for (uint256 i = 0; i < tokenIds.length; ) { + IERC1155MetadataURI(rootToken).safeTransferFrom(address(this), receivers[i], tokenIds[i], amounts[i], ""); + unchecked { + ++i; + } + } + // slither-disable-next-line reentrancy-events + emit L2MintableERC1155WithdrawBatch(address(rootToken), childToken, withdrawer, receivers, tokenIds, amounts); + } + + function _getChildToken(IERC1155MetadataURI rootToken) private returns (address childToken) { + childToken = rootTokenToChildToken[address(rootToken)]; + if (childToken == address(0)) childToken = mapToken(IERC1155MetadataURI(rootToken)); + assert(childToken != address(0)); // invariant because we map the token if mapping does not exist + } +} diff --git a/contracts/child/RootMintableERC20Predicate.sol b/contracts/child/RootMintableERC20Predicate.sol new file mode 100644 index 00000000..eda0a349 --- /dev/null +++ b/contracts/child/RootMintableERC20Predicate.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "../interfaces/child/IRootMintableERC20Predicate.sol"; +import "../interfaces/IStateSender.sol"; +import "./System.sol"; + +// solhint-disable reason-string +contract RootMintableERC20Predicate is IRootMintableERC20Predicate, Initializable, System { + using SafeERC20 for IERC20Metadata; + + /// @custom:security write-protection="onlySystemCall()" + IStateSender public l2StateSender; + /// @custom:security write-protection="onlySystemCall()" + address public stateReceiver; + /// @custom:security write-protection="onlySystemCall()" + address public childERC20Predicate; + /// @custom:security write-protection="onlySystemCall()" + address public childTokenTemplate; + + bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); + bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW"); + bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); + mapping(address => address) public rootTokenToChildToken; + + /** + * @notice Initilization function for RootMintableERC20Predicate + * @param newL2StateSender Address of L2StateSender to send exit information to + * @param newStateReceiver Address of StateReceiver to receive deposit information from + * @param newChildERC20Predicate Address of child ERC20 predicate to communicate with + * @param newChildTokenTemplate Address of child token implementation to deploy clones of + * @dev Can only be called once. + */ + function initialize( + address newL2StateSender, + address newStateReceiver, + address newChildERC20Predicate, + address newChildTokenTemplate + ) public virtual onlySystemCall initializer { + _initialize(newL2StateSender, newStateReceiver, newChildERC20Predicate, newChildTokenTemplate); + } + + /** + * @notice Function to be used for token withdrawals + * @param sender Address of the sender on the root chain + * @param data Data sent by the sender + * @dev Can be extended to include other signatures for more functionality + */ + function onStateReceive(uint256 /* id */, address sender, bytes calldata data) external { + require(msg.sender == stateReceiver, "RootMintableERC20Predicate: ONLY_STATE_RECEIVER"); + require(sender == childERC20Predicate, "RootMintableERC20Predicate: ONLY_CHILD_PREDICATE"); + + if (bytes32(data[:32]) == WITHDRAW_SIG) { + _beforeTokenWithdraw(); + _withdraw(data[32:]); + _afterTokenWithdraw(); + } else { + revert("RootMintableERC20Predicate: INVALID_SIGNATURE"); + } + } + + /** + * @inheritdoc IRootMintableERC20Predicate + */ + function deposit(IERC20Metadata rootToken, uint256 amount) external { + _deposit(rootToken, msg.sender, amount); + } + + /** + * @inheritdoc IRootMintableERC20Predicate + */ + function depositTo(IERC20Metadata rootToken, address receiver, uint256 amount) external { + _deposit(rootToken, receiver, amount); + } + + /** + * @inheritdoc IRootMintableERC20Predicate + */ + function mapToken(IERC20Metadata rootToken) public returns (address) { + require(address(rootToken) != address(0), "RootMintableERC20Predicate: INVALID_TOKEN"); + require(rootTokenToChildToken[address(rootToken)] == address(0), "RootMintableERC20Predicate: ALREADY_MAPPED"); + + address childPredicate = childERC20Predicate; + + address childToken = Clones.predictDeterministicAddress( + childTokenTemplate, + keccak256(abi.encodePacked(rootToken)), + childPredicate + ); + + rootTokenToChildToken[address(rootToken)] = childToken; + + l2StateSender.syncState( + childPredicate, + abi.encode(MAP_TOKEN_SIG, rootToken, rootToken.name(), rootToken.symbol(), rootToken.decimals()) + ); + // slither-disable-next-line reentrancy-events + emit L2MintableTokenMapped(address(rootToken), childToken); + + return childToken; + } + + /** + * @notice Internal initialization function for RootMintableERC20Predicate + * @param newL2StateSender Address of L2StateSender to send exit information to + * @param newStateReceiver Address of StateReceiver to receive deposit information from + * @param newChildERC20Predicate Address of root ERC20 predicate to communicate with + * @param newChildTokenTemplate Address of child token implementation to deploy clones of + * @dev Can be called multiple times. + */ + function _initialize( + address newL2StateSender, + address newStateReceiver, + address newChildERC20Predicate, + address newChildTokenTemplate + ) internal { + require( + newL2StateSender != address(0) && + newStateReceiver != address(0) && + newChildERC20Predicate != address(0) && + newChildTokenTemplate != address(0), + "RootMintableERC20Predicate: BAD_INITIALIZATION" + ); + l2StateSender = IStateSender(newL2StateSender); + stateReceiver = newStateReceiver; + childERC20Predicate = newChildERC20Predicate; + childTokenTemplate = newChildTokenTemplate; + } + + // solhint-disable no-empty-blocks + // slither-disable-start dead-code + function _beforeTokenDeposit() internal virtual {} + + function _beforeTokenWithdraw() internal virtual {} + + function _afterTokenDeposit() internal virtual {} + + function _afterTokenWithdraw() internal virtual {} + + // slither-disable-end dead-code + + function _deposit(IERC20Metadata rootToken, address receiver, uint256 amount) private { + _beforeTokenDeposit(); + address childToken = rootTokenToChildToken[address(rootToken)]; + + if (childToken == address(0)) { + childToken = mapToken(rootToken); + } + + assert(childToken != address(0)); // invariant because we map the token if mapping does not exist + + rootToken.safeTransferFrom(msg.sender, address(this), amount); + + l2StateSender.syncState(childERC20Predicate, abi.encode(DEPOSIT_SIG, rootToken, msg.sender, receiver, amount)); + // slither-disable-next-line reentrancy-events + emit L2MintableERC20Deposit(address(rootToken), childToken, msg.sender, receiver, amount); + _afterTokenDeposit(); + } + + function _withdraw(bytes calldata data) private { + (address rootToken, address withdrawer, address receiver, uint256 amount) = abi.decode( + data, + (address, address, address, uint256) + ); + address childToken = rootTokenToChildToken[rootToken]; + assert(childToken != address(0)); // invariant because child predicate should have already mapped tokens + + IERC20Metadata(rootToken).safeTransfer(receiver, amount); + // slither-disable-next-line reentrancy-events + emit L2MintableERC20Withdraw(address(rootToken), childToken, withdrawer, receiver, amount); + } +} diff --git a/contracts/child/RootMintableERC721Predicate.sol b/contracts/child/RootMintableERC721Predicate.sol new file mode 100644 index 00000000..168ee574 --- /dev/null +++ b/contracts/child/RootMintableERC721Predicate.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "../interfaces/child/IRootMintableERC721Predicate.sol"; +import "../interfaces/IStateSender.sol"; +import "./System.sol"; + +// solhint-disable reason-string +contract RootMintableERC721Predicate is Initializable, ERC721Holder, System, IRootMintableERC721Predicate { + IStateSender public l2StateSender; + address public stateReceiver; + address public childERC721Predicate; + address public childTokenTemplate; + bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); + bytes32 public constant DEPOSIT_BATCH_SIG = keccak256("DEPOSIT_BATCH"); + bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW"); + bytes32 public constant WITHDRAW_BATCH_SIG = keccak256("WITHDRAW_BATCH"); + bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); + mapping(address => address) public rootTokenToChildToken; + + /** + * @notice Initilization function for RootMintableERC721Predicate + * @param newL2StateSender Address of L2StateSender to send deposit information to + * @param newStateReceiver Address of StateReceiver to receive withdrawal information from + * @param newChildERC721Predicate Address of child ERC721 predicate to communicate with + * @param newChildTokenTemplate Address of child token template to calculate child token addresses + * @dev Can only be called once. + */ + function initialize( + address newL2StateSender, + address newStateReceiver, + address newChildERC721Predicate, + address newChildTokenTemplate + ) external virtual onlySystemCall initializer { + _initialize(newL2StateSender, newStateReceiver, newChildERC721Predicate, newChildTokenTemplate); + } + + /** + * @inheritdoc IStateReceiver + * @notice Function to be used for token withdrawals + * @dev Can be extended to include other signatures for more functionality + */ + function onStateReceive(uint256 /* id */, address sender, bytes calldata data) external { + require(msg.sender == stateReceiver, "RootMintableERC721Predicate: ONLY_STATE_RECEIVER"); + require(sender == childERC721Predicate, "RootMintableERC721Predicate: ONLY_CHILD_PREDICATE"); + + if (bytes32(data[:32]) == WITHDRAW_SIG) { + _withdraw(data[32:]); + } else if (bytes32(data[:32]) == WITHDRAW_BATCH_SIG) { + _withdrawBatch(data); + } else { + revert("RootMintableERC721Predicate: INVALID_SIGNATURE"); + } + } + + /** + * @inheritdoc IRootMintableERC721Predicate + */ + function deposit(IERC721Metadata rootToken, uint256 tokenId) external { + _deposit(rootToken, msg.sender, tokenId); + } + + /** + * @inheritdoc IRootMintableERC721Predicate + */ + function depositTo(IERC721Metadata rootToken, address receiver, uint256 tokenId) external { + _deposit(rootToken, receiver, tokenId); + } + + /** + * @inheritdoc IRootMintableERC721Predicate + */ + function depositBatch( + IERC721Metadata rootToken, + address[] calldata receivers, + uint256[] calldata tokenIds + ) external { + require(receivers.length == tokenIds.length, "RootMintableERC721Predicate: INVALID_LENGTH"); + _depositBatch(rootToken, receivers, tokenIds); + } + + /** + * @inheritdoc IRootMintableERC721Predicate + */ + function mapToken(IERC721Metadata rootToken) public returns (address) { + require(address(rootToken) != address(0), "RootMintableERC721Predicate: INVALID_TOKEN"); + require(rootTokenToChildToken[address(rootToken)] == address(0), "RootMintableERC721Predicate: ALREADY_MAPPED"); + + address childToken = Clones.predictDeterministicAddress( + childTokenTemplate, + keccak256(abi.encodePacked(rootToken)), + childERC721Predicate + ); + + rootTokenToChildToken[address(rootToken)] = childToken; + + l2StateSender.syncState( + childERC721Predicate, + abi.encode(MAP_TOKEN_SIG, rootToken, rootToken.name(), rootToken.symbol()) + ); + // slither-disable-next-line reentrancy-events + emit L2MintableTokenMapped(address(rootToken), childToken); + return childToken; + } + + function _initialize( + address newL2StateSender, + address newStateReceiver, + address newChildERC721Predicate, + address newChildTokenTemplate + ) internal { + require( + newL2StateSender != address(0) && + newStateReceiver != address(0) && + newChildERC721Predicate != address(0) && + newChildTokenTemplate != address(0), + "RootMintableERC721Predicate: BAD_INITIALIZATION" + ); + l2StateSender = IStateSender(newL2StateSender); + stateReceiver = newStateReceiver; + childERC721Predicate = newChildERC721Predicate; + childTokenTemplate = newChildTokenTemplate; + } + + function _deposit(IERC721Metadata rootToken, address receiver, uint256 tokenId) private { + address childToken = _getChildToken(rootToken); + + rootToken.safeTransferFrom(msg.sender, address(this), tokenId); + + l2StateSender.syncState( + childERC721Predicate, + abi.encode(DEPOSIT_SIG, rootToken, msg.sender, receiver, tokenId) + ); + // slither-disable-next-line reentrancy-events + emit L2MintableERC721Deposit(address(rootToken), childToken, msg.sender, receiver, tokenId); + } + + function _depositBatch( + IERC721Metadata rootToken, + address[] calldata receivers, + uint256[] calldata tokenIds + ) private { + address childToken = _getChildToken(rootToken); + + for (uint256 i = 0; i < tokenIds.length; ) { + rootToken.safeTransferFrom(msg.sender, address(this), tokenIds[i]); + unchecked { + ++i; + } + } + + l2StateSender.syncState( + childERC721Predicate, + abi.encode(DEPOSIT_BATCH_SIG, rootToken, msg.sender, receivers, tokenIds) + ); + // slither-disable-next-line reentrancy-events + emit L2MintableERC721DepositBatch(address(rootToken), childToken, msg.sender, receivers, tokenIds); + } + + function _withdraw(bytes calldata data) private { + (address rootToken, address withdrawer, address receiver, uint256 tokenId) = abi.decode( + data, + (address, address, address, uint256) + ); + address childToken = rootTokenToChildToken[rootToken]; + assert(childToken != address(0)); // invariant because child predicate should have already mapped tokens + + IERC721Metadata(rootToken).safeTransferFrom(address(this), receiver, tokenId); + // slither-disable-next-line reentrancy-events + emit L2MintableERC721Withdraw(address(rootToken), childToken, withdrawer, receiver, tokenId); + } + + function _withdrawBatch(bytes calldata data) private { + (, address rootToken, address withdrawer, address[] memory receivers, uint256[] memory tokenIds) = abi.decode( + data, + (bytes32, address, address, address[], uint256[]) + ); + address childToken = rootTokenToChildToken[rootToken]; + assert(childToken != address(0)); // invariant because child predicate should have already mapped tokens + for (uint256 i = 0; i < tokenIds.length; ) { + IERC721Metadata(rootToken).safeTransferFrom(address(this), receivers[i], tokenIds[i]); + unchecked { + ++i; + } + } + // slither-disable-next-line reentrancy-events + emit L2MintableERC721WithdrawBatch(address(rootToken), childToken, withdrawer, receivers, tokenIds); + } + + function _getChildToken(IERC721Metadata rootToken) private returns (address childToken) { + childToken = rootTokenToChildToken[address(rootToken)]; + if (childToken == address(0)) childToken = mapToken(IERC721Metadata(rootToken)); + assert(childToken != address(0)); // invariant because we map the token if mapping does not exist + } +} diff --git a/contracts/interfaces/child/IChildERC20Predicate.sol b/contracts/interfaces/child/IChildERC20Predicate.sol index 55dae6ed..0e8679cb 100644 --- a/contracts/interfaces/child/IChildERC20Predicate.sol +++ b/contracts/interfaces/child/IChildERC20Predicate.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import "./IChildERC20.sol"; import "./IStateReceiver.sol"; +import "./IChildERC20.sol"; interface IChildERC20Predicate is IStateReceiver { function initialize( diff --git a/contracts/interfaces/child/IRootMintableERC1155Predicate.sol b/contracts/interfaces/child/IRootMintableERC1155Predicate.sol new file mode 100644 index 00000000..4be3bac2 --- /dev/null +++ b/contracts/interfaces/child/IRootMintableERC1155Predicate.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; +import "./IStateReceiver.sol"; + +interface IRootMintableERC1155Predicate is IStateReceiver { + struct L2MintableERC1155BridgeEvent { + address rootToken; + address childToken; + address sender; + address receiver; + } + + event L2MintableERC1155Deposit( + address indexed rootToken, + address indexed childToken, + address depositor, + address indexed receiver, + uint256 tokenId, + uint256 amount + ); + event L2MintableERC1155DepositBatch( + address indexed rootToken, + address indexed childToken, + address indexed depositor, + address[] receivers, + uint256[] tokenIds, + uint256[] amounts + ); + event L2MintableERC1155Withdraw( + address indexed rootToken, + address indexed childToken, + address withdrawer, + address indexed receiver, + uint256 tokenId, + uint256 amount + ); + event L2MintableERC1155WithdrawBatch( + address indexed rootToken, + address indexed childToken, + address indexed withdrawer, + address[] receivers, + uint256[] tokenIds, + uint256[] amounts + ); + event L2MintableTokenMapped(address indexed rootToken, address indexed childToken); + + /** + * @notice Function to deposit tokens from the depositor to themselves on the child chain + * @param rootToken Address of the root token being deposited + * @param tokenId Index of the NFT to deposit + * @param amount Amount to deposit + */ + function deposit(IERC1155MetadataURI rootToken, uint256 tokenId, uint256 amount) external; + + /** + * @notice Function to deposit tokens from the depositor to another address on the child chain + * @param rootToken Address of the root token being deposited + * @param tokenId Index of the NFT to deposit + * @param amount Amount to deposit + */ + function depositTo(IERC1155MetadataURI rootToken, address receiver, uint256 tokenId, uint256 amount) external; + + /** + * @notice Function to deposit tokens from the depositor to other addresses on the child chain + * @param rootToken Address of the root token being deposited + * @param receivers Addresses of the receivers on the child chain + * @param tokenIds Indeices of the NFTs to deposit + * @param amounts Amounts to deposit + */ + function depositBatch( + IERC1155MetadataURI rootToken, + address[] calldata receivers, + uint256[] calldata tokenIds, + uint256[] calldata amounts + ) external; + + /** + * @notice Function to be used for token mapping + * @param rootToken Address of the root token to map + * @return childToken Address of the mapped child token + * @dev Called internally on deposit if token is not mapped already + */ + function mapToken(IERC1155MetadataURI rootToken) external returns (address childToken); +} diff --git a/contracts/interfaces/child/IRootMintableERC20Predicate.sol b/contracts/interfaces/child/IRootMintableERC20Predicate.sol new file mode 100644 index 00000000..453dd8f1 --- /dev/null +++ b/contracts/interfaces/child/IRootMintableERC20Predicate.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "./IStateReceiver.sol"; + +interface IRootMintableERC20Predicate is IStateReceiver { + event L2MintableERC20Deposit( + address indexed rootToken, + address indexed childToken, + address depositor, + address indexed receiver, + uint256 amount + ); + event L2MintableERC20Withdraw( + address indexed rootToken, + address indexed childToken, + address withdrawer, + address indexed receiver, + uint256 amount + ); + event L2MintableTokenMapped(address indexed rootToken, address indexed childToken); + + /** + * @notice Function to deposit tokens from the depositor to themselves on the child chain + * @param rootToken Address of the root token being deposited + * @param amount Amount to deposit + */ + function deposit(IERC20Metadata rootToken, uint256 amount) external; + + /** + * @notice Function to deposit tokens from the depositor to another address on the child chain + * @param rootToken Address of the root token being deposited + * @param amount Amount to deposit + */ + function depositTo(IERC20Metadata rootToken, address receiver, uint256 amount) external; + + /** + * @notice Function to be used for token mapping + * @param rootToken Address of the root token to map + * @dev Called internally on deposit if token is not mapped already + * @return address Address of the child token + */ + function mapToken(IERC20Metadata rootToken) external returns (address); +} diff --git a/contracts/interfaces/child/IRootMintableERC721Predicate.sol b/contracts/interfaces/child/IRootMintableERC721Predicate.sol new file mode 100644 index 00000000..929a1d0d --- /dev/null +++ b/contracts/interfaces/child/IRootMintableERC721Predicate.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import "./IStateReceiver.sol"; + +interface IRootMintableERC721Predicate is IStateReceiver { + event L2MintableERC721Deposit( + address indexed rootToken, + address indexed childToken, + address depositor, + address indexed receiver, + uint256 tokenId + ); + event L2MintableERC721DepositBatch( + address indexed rootToken, + address indexed childToken, + address indexed depositor, + address[] receivers, + uint256[] tokenIds + ); + event L2MintableERC721Withdraw( + address indexed rootToken, + address indexed childToken, + address withdrawer, + address indexed receiver, + uint256 tokenId + ); + event L2MintableERC721WithdrawBatch( + address indexed rootToken, + address indexed childToken, + address indexed withdrawer, + address[] receivers, + uint256[] tokenIds + ); + event L2MintableTokenMapped(address indexed rootToken, address indexed childToken); + + /** + * @notice Function to deposit tokens from the depositor to themselves on the child chain + * @param rootToken Address of the root token being deposited + * @param tokenId Index of the NFT to deposit + */ + function deposit(IERC721Metadata rootToken, uint256 tokenId) external; + + /** + * @notice Function to deposit tokens from the depositor to another address on the child chain + * @param rootToken Address of the root token being deposited + * @param tokenId Index of the NFT to deposit + */ + function depositTo(IERC721Metadata rootToken, address receiver, uint256 tokenId) external; + + /** + * @notice Function to deposit tokens from the depositor to other addresses on the child chain + * @param rootToken Address of the root token being deposited + * @param receivers Addresses of the receivers on the child chain + * @param tokenIds Indeices of the NFTs to deposit + */ + function depositBatch( + IERC721Metadata rootToken, + address[] calldata receivers, + uint256[] calldata tokenIds + ) external; + + /** + * @notice Function to be used for token mapping + * @param rootToken Address of the root token to map + * @return childToken Address of the mapped child token + * @dev Called internally on deposit if token is not mapped already + */ + function mapToken(IERC721Metadata rootToken) external returns (address childToken); +} diff --git a/contracts/interfaces/root/IChildMintableERC1155Predicate.sol b/contracts/interfaces/root/IChildMintableERC1155Predicate.sol new file mode 100644 index 00000000..bd36afa6 --- /dev/null +++ b/contracts/interfaces/root/IChildMintableERC1155Predicate.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "../child/IChildERC1155.sol"; +import "./IL2StateReceiver.sol"; + +interface IChildMintableERC1155Predicate is IL2StateReceiver { + event MintableERC1155Deposit( + address indexed rootToken, + address indexed childToken, + address sender, + address indexed receiver, + uint256 tokenId, + uint256 amount + ); + event MintableERC1155DepositBatch( + address indexed rootToken, + address indexed childToken, + address indexed sender, + address[] receivers, + uint256[] tokenIds, + uint256[] amounts + ); + event MintableERC1155Withdraw( + address indexed rootToken, + address indexed childToken, + address sender, + address indexed receiver, + uint256 tokenId, + uint256 amount + ); + event MintableERC1155WithdrawBatch( + address indexed rootToken, + address indexed childToken, + address indexed sender, + address[] receivers, + uint256[] tokenIds, + uint256[] amounts + ); + event MintableTokenMapped(address indexed rootToken, address indexed childToken); + + function initialize( + address newStateSender, + address newExitHelper, + address newRootERC721Predicate, + address newChildTokenTemplate + ) external; + + function withdraw(IChildERC1155 childToken, uint256 tokenId, uint256 amount) external; + + function withdrawTo(IChildERC1155 childToken, address receiver, uint256 tokenId, uint256 amount) external; +} diff --git a/contracts/interfaces/root/IChildMintableERC20Predicate.sol b/contracts/interfaces/root/IChildMintableERC20Predicate.sol new file mode 100644 index 00000000..0621bc23 --- /dev/null +++ b/contracts/interfaces/root/IChildMintableERC20Predicate.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "../child/IChildERC20.sol"; +import "./IL2StateReceiver.sol"; + +interface IChildMintableERC20Predicate is IL2StateReceiver { + event MintableERC20Deposit( + address indexed rootToken, + address indexed childToken, + address sender, + address indexed receiver, + uint256 amount + ); + event MintableERC20Withdraw( + address indexed rootToken, + address indexed childToken, + address sender, + address indexed receiver, + uint256 amount + ); + event MintableTokenMapped(address indexed rootToken, address indexed childToken); + + function initialize( + address newL2StateSender, + address newStateReceiver, + address newRootERC20Predicate, + address newChildTokenTemplate + ) external; + + function withdraw(IChildERC20 childToken, uint256 amount) external; + + function withdrawTo(IChildERC20 childToken, address receiver, uint256 amount) external; +} diff --git a/contracts/interfaces/root/IChildMintableERC721Predicate.sol b/contracts/interfaces/root/IChildMintableERC721Predicate.sol new file mode 100644 index 00000000..f6e553c5 --- /dev/null +++ b/contracts/interfaces/root/IChildMintableERC721Predicate.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "../child/IChildERC721.sol"; +import "./IL2StateReceiver.sol"; + +interface IChildMintableERC721Predicate is IL2StateReceiver { + event MintableERC721Deposit( + address indexed rootToken, + address indexed childToken, + address sender, + address indexed receiver, + uint256 tokenId + ); + event MintableERC721DepositBatch( + address indexed rootToken, + address indexed childToken, + address indexed sender, + address[] receivers, + uint256[] tokenIds + ); + event MintableERC721Withdraw( + address indexed rootToken, + address indexed childToken, + address sender, + address indexed receiver, + uint256 tokenId + ); + event MintableERC721WithdrawBatch( + address indexed rootToken, + address indexed childToken, + address indexed sender, + address[] receivers, + uint256[] tokenIds + ); + event MintableTokenMapped(address indexed rootToken, address indexed childToken); + + function initialize( + address newStateSender, + address newExitHelper, + address newRootERC721Predicate, + address newChildTokenTemplate + ) external; + + function onL2StateReceive(uint256 /* id */, address sender, bytes calldata data) external; + + function withdraw(IChildERC721 childToken, uint256 tokenId) external; + + function withdrawTo(IChildERC721 childToken, address receiver, uint256 tokenId) external; + + function withdrawBatch(IChildERC721 childToken, address[] calldata receivers, uint256[] calldata tokenIds) external; +} diff --git a/contracts/interfaces/root/IRootERC1155Predicate.sol b/contracts/interfaces/root/IRootERC1155Predicate.sol index 230f2bc2..654ad943 100644 --- a/contracts/interfaces/root/IRootERC1155Predicate.sol +++ b/contracts/interfaces/root/IRootERC1155Predicate.sol @@ -2,8 +2,9 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; +import "./IL2StateReceiver.sol"; -interface IRootERC1155Predicate { +interface IRootERC1155Predicate is IL2StateReceiver { struct ERC1155BridgeEvent { address rootToken; address childToken; diff --git a/contracts/interfaces/root/IRootERC20Predicate.sol b/contracts/interfaces/root/IRootERC20Predicate.sol index 1f42706d..8706fe78 100644 --- a/contracts/interfaces/root/IRootERC20Predicate.sol +++ b/contracts/interfaces/root/IRootERC20Predicate.sol @@ -2,8 +2,9 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "./IL2StateReceiver.sol"; -interface IRootERC20Predicate { +interface IRootERC20Predicate is IL2StateReceiver { struct ERC20BridgeEvent { address rootToken; address childToken; diff --git a/contracts/interfaces/root/IRootERC721Predicate.sol b/contracts/interfaces/root/IRootERC721Predicate.sol index 03960cdd..9aa7f9aa 100644 --- a/contracts/interfaces/root/IRootERC721Predicate.sol +++ b/contracts/interfaces/root/IRootERC721Predicate.sol @@ -2,15 +2,9 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import "./IL2StateReceiver.sol"; -interface IRootERC721Predicate { - struct ERC721BridgeEvent { - address rootToken; - address childToken; - address sender; - address receiver; - } - +interface IRootERC721Predicate is IL2StateReceiver { event ERC721Deposit( address indexed rootToken, address indexed childToken, diff --git a/contracts/root/ChildMintableERC1155Predicate.sol b/contracts/root/ChildMintableERC1155Predicate.sol new file mode 100644 index 00000000..80342e43 --- /dev/null +++ b/contracts/root/ChildMintableERC1155Predicate.sol @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "../interfaces/root/IChildMintableERC1155Predicate.sol"; +import "../interfaces/child/IChildERC1155.sol"; +import "../interfaces/IStateSender.sol"; + +/** + @title ChildMintableERC1155Predicate + @author Polygon Technology (@QEDK) + @notice Enables mintable ERC1155 token deposits and withdrawals across an arbitrary root chain and child chain + */ +// solhint-disable reason-string +contract ChildMintableERC1155Predicate is Initializable, IChildMintableERC1155Predicate { + IStateSender public stateSender; + address public exitHelper; + address public rootERC1155Predicate; + address public childTokenTemplate; + bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); + bytes32 public constant DEPOSIT_BATCH_SIG = keccak256("DEPOSIT_BATCH"); + bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW"); + bytes32 public constant WITHDRAW_BATCH_SIG = keccak256("WITHDRAW_BATCH"); + bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); + + mapping(address => address) public rootTokenToChildToken; + + modifier onlyValidToken(IChildERC1155 childToken) { + require(_verifyContract(childToken), "ChildMintableERC1155Predicate: NOT_CONTRACT"); + _; + } + + /** + * @notice Initilization function for ChildMintableERC1155Predicate + * @param newStateSender Address of StateSender to send exit information to + * @param newExitHelper Address of ExitHelper to receive deposit information from + * @param newRootERC1155Predicate Address of root ERC1155 predicate to communicate with + * @param newChildTokenTemplate Address of child token implementation to deploy clones of + * @dev Can only be called once. + */ + function initialize( + address newStateSender, + address newExitHelper, + address newRootERC1155Predicate, + address newChildTokenTemplate + ) public virtual initializer { + _initialize(newStateSender, newExitHelper, newRootERC1155Predicate, newChildTokenTemplate); + } + + /** + * @notice Function to be used for token deposits + * @param sender Address of the sender on the root chain + * @param data Data sent by the sender + * @dev Can be extended to include other signatures for more functionality + */ + function onL2StateReceive(uint256 /* id */, address sender, bytes calldata data) external { + require(msg.sender == exitHelper, "ChildMintableERC1155Predicate: ONLY_EXIT_HELPER"); + require(sender == rootERC1155Predicate, "ChildMintableERC1155Predicate: ONLY_ROOT_PREDICATE"); + + if (bytes32(data[:32]) == DEPOSIT_SIG) { + _beforeTokenDeposit(); + _deposit(data[32:]); + _afterTokenDeposit(); + } else if (bytes32(data[:32]) == DEPOSIT_BATCH_SIG) { + _beforeTokenDeposit(); + _depositBatch(data); + _afterTokenDeposit(); + } else if (bytes32(data[:32]) == MAP_TOKEN_SIG) { + _mapToken(data); + } else { + revert("ChildMintableERC1155Predicate: INVALID_SIGNATURE"); + } + } + + /** + * @notice Function to withdraw tokens from the withdrawer to themselves on the root chain + * @param childToken Address of the child token being withdrawn + * @param tokenId Index of the NFT to withdraw + * @param amount Amount of the NFT to withdraw + */ + function withdraw(IChildERC1155 childToken, uint256 tokenId, uint256 amount) external { + _beforeTokenWithdraw(); + _withdraw(childToken, msg.sender, tokenId, amount); + _afterTokenWithdraw(); + } + + /** + * @notice Function to withdraw tokens from the withdrawer to another address on the root chain + * @param childToken Address of the child token being withdrawn + * @param receiver Address of the receiver on the root chain + * @param tokenId Index of the NFT to withdraw + * @param amount Amount of NFT to withdraw + */ + function withdrawTo(IChildERC1155 childToken, address receiver, uint256 tokenId, uint256 amount) external { + _beforeTokenWithdraw(); + _withdraw(childToken, receiver, tokenId, amount); + _afterTokenWithdraw(); + } + + /** + * @notice Function to batch withdraw tokens from the withdrawer to other addresses on the root chain + * @param childToken Address of the child token being withdrawn + * @param receivers Addresses of the receivers on the root chain + * @param tokenIds indices of the NFTs to withdraw + * @param amounts Amounts of NFTs to withdraw + */ + function withdrawBatch( + IChildERC1155 childToken, + address[] calldata receivers, + uint256[] calldata tokenIds, + uint256[] calldata amounts + ) external { + _beforeTokenWithdraw(); + _withdrawBatch(childToken, receivers, tokenIds, amounts); + _afterTokenWithdraw(); + } + + /** + * @notice Internal initilization function for ChildMintableERC1155Predicate + * @param newStateSender Address of StateSender to send exit information to + * @param newExitHelper Address of ExitHelper to receive deposit information from + * @param newRootERC1155Predicate Address of root ERC1155 predicate to communicate with + * @param newChildTokenTemplate Address of child token implementation to deploy clones of + * @dev Can be called multiple times. + */ + function _initialize( + address newStateSender, + address newExitHelper, + address newRootERC1155Predicate, + address newChildTokenTemplate + ) internal { + require( + newStateSender != address(0) && + newExitHelper != address(0) && + newRootERC1155Predicate != address(0) && + newChildTokenTemplate != address(0), + "ChildMintableERC1155Predicate: BAD_INITIALIZATION" + ); + stateSender = IStateSender(newStateSender); + exitHelper = newExitHelper; + rootERC1155Predicate = newRootERC1155Predicate; + childTokenTemplate = newChildTokenTemplate; + } + + // solhint-disable no-empty-blocks + // slither-disable-start dead-code + function _beforeTokenDeposit() internal virtual {} + + function _beforeTokenWithdraw() internal virtual {} + + function _afterTokenDeposit() internal virtual {} + + function _afterTokenWithdraw() internal virtual {} + + // slither-disable-end dead-code + + function _withdraw( + IChildERC1155 childToken, + address receiver, + uint256 tokenId, + uint256 amount + ) private onlyValidToken(childToken) { + address rootToken = childToken.rootToken(); + + require( + rootTokenToChildToken[rootToken] == address(childToken), + "ChildMintableERC1155Predicate: UNMAPPED_TOKEN" + ); + // a mapped token should never have root token unset + assert(rootToken != address(0)); + // a mapped token should never have predicate unset + assert(childToken.predicate() == address(this)); + + require(childToken.burn(msg.sender, tokenId, amount), "ChildMintableERC1155Predicate: BURN_FAILED"); + stateSender.syncState( + rootERC1155Predicate, + abi.encode(WITHDRAW_SIG, rootToken, msg.sender, receiver, tokenId, amount) + ); + // slither-disable-next-line reentrancy-events + emit MintableERC1155Withdraw(rootToken, address(childToken), msg.sender, receiver, tokenId, amount); + } + + function _withdrawBatch( + IChildERC1155 childToken, + address[] calldata receivers, + uint256[] calldata tokenIds, + uint256[] calldata amounts + ) private onlyValidToken(childToken) { + address rootToken = childToken.rootToken(); + + require( + rootTokenToChildToken[rootToken] == address(childToken), + "ChildMintableERC1155Predicate: UNMAPPED_TOKEN" + ); + // a mapped token should never have root token unset + assert(rootToken != address(0)); + // a mapped token should never have predicate unset + assert(childToken.predicate() == address(this)); + + require( + receivers.length == tokenIds.length && tokenIds.length == amounts.length, + "ChildMintableERC1155Predicate: INVALID_LENGTH" + ); + + require(childToken.burnBatch(msg.sender, tokenIds, amounts), "ChildMintableERC1155Predicate: BURN_FAILED"); + + stateSender.syncState( + rootERC1155Predicate, + abi.encode(WITHDRAW_BATCH_SIG, rootToken, msg.sender, receivers, tokenIds, amounts) + ); + // slither-disable-next-line reentrancy-events + emit MintableERC1155WithdrawBatch(rootToken, address(childToken), msg.sender, receivers, tokenIds, amounts); + } + + function _deposit(bytes calldata data) private { + (address depositToken, address depositor, address receiver, uint256 tokenId, uint256 amount) = abi.decode( + data, + (address, address, address, uint256, uint256) + ); + + IChildERC1155 childToken = IChildERC1155(rootTokenToChildToken[depositToken]); + + require(address(childToken) != address(0), "ChildMintableERC1155Predicate: UNMAPPED_TOKEN"); + // a mapped token should always pass specifications + assert(_verifyContract(childToken)); + + address rootToken = IChildERC1155(childToken).rootToken(); + + // a mapped child token should match deposited token + assert(rootToken == depositToken); + // a mapped token should never have root token unset + assert(rootToken != address(0)); + // a mapped token should never have predicate unset + assert(IChildERC1155(childToken).predicate() == address(this)); + require( + IChildERC1155(childToken).mint(receiver, tokenId, amount), + "ChildMintableERC1155Predicate: MINT_FAILED" + ); + // slither-disable-next-line reentrancy-events + emit MintableERC1155Deposit(depositToken, address(childToken), depositor, receiver, tokenId, amount); + } + + function _depositBatch(bytes calldata data) private { + ( + , + address depositToken, + address depositor, + address[] memory receivers, + uint256[] memory tokenIds, + uint256[] memory amounts + ) = abi.decode(data, (bytes32, address, address, address[], uint256[], uint256[])); + + IChildERC1155 childToken = IChildERC1155(rootTokenToChildToken[depositToken]); + + require(address(childToken) != address(0), "ChildMintableERC1155Predicate: UNMAPPED_TOKEN"); + // a mapped token should always pass specifications + assert(_verifyContract(childToken)); + + address rootToken = IChildERC1155(childToken).rootToken(); + + // a mapped child token should match deposited token + assert(rootToken == depositToken); + // a mapped token should never have root token unset + assert(rootToken != address(0)); + // a mapped token should never have predicate unset + assert(IChildERC1155(childToken).predicate() == address(this)); + require( + IChildERC1155(childToken).mintBatch(receivers, tokenIds, amounts), + "ChildMintableERC1155Predicate: MINT_FAILED" + ); + // slither-disable-next-line reentrancy-events + emit MintableERC1155DepositBatch(depositToken, address(childToken), depositor, receivers, tokenIds, amounts); + } + + /** + * @notice Function to be used for mapping a root token to a child token + * @dev Allows for 1-to-1 mappings for any root token to a child token + */ + function _mapToken(bytes calldata data) private { + (, address rootToken, string memory uri_) = abi.decode(data, (bytes32, address, string)); + assert(rootToken != address(0)); // invariant since root predicate performs the same check + assert(rootTokenToChildToken[rootToken] == address(0)); // invariant since root predicate performs the same check + IChildERC1155 childToken = IChildERC1155( + Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(rootToken))) + ); + rootTokenToChildToken[rootToken] = address(childToken); + childToken.initialize(rootToken, uri_); + + // slither-disable-next-line reentrancy-events + emit MintableTokenMapped(rootToken, address(childToken)); + } + + // slither does not handle try-catch blocks correctly + // slither-disable-next-line unused-return + function _verifyContract(IChildERC1155 childToken) private view returns (bool) { + if (address(childToken).code.length == 0) { + return false; + } + // slither-disable-next-line uninitialized-local,variable-scope + try childToken.supportsInterface(0xd9b67a26) returns (bool support) { + return support; + } catch { + return false; + } + } +} diff --git a/contracts/root/ChildMintableERC20Predicate.sol b/contracts/root/ChildMintableERC20Predicate.sol new file mode 100644 index 00000000..0630a70a --- /dev/null +++ b/contracts/root/ChildMintableERC20Predicate.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "../interfaces/root/IChildMintableERC20Predicate.sol"; +import "../interfaces/child/IChildERC20.sol"; +import "../interfaces/IStateSender.sol"; + +/** + @title ChildMintableERC20Predicate + @author Polygon Technology (@QEDK) + @notice Enables ERC20 token deposits and withdrawals across an arbitrary root chain and child chain + */ +// solhint-disable reason-string +contract ChildMintableERC20Predicate is Initializable, IChildMintableERC20Predicate { + using SafeERC20 for IERC20; + + IStateSender public stateSender; + address public exitHelper; + address public rootERC20Predicate; + address public childTokenTemplate; + bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); + bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW"); + bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); + + mapping(address => address) public rootTokenToChildToken; + + /** + * @notice Initilization function for ChildMintableERC20Predicate + * @param newStateSender Address of StateSender to send deposit information to + * @param newExitHelper Address of ExitHelper to receive withdrawal information from + * @param newRootERC20Predicate Address of root ERC20 predicate to communicate with + * @dev Can only be called once. + */ + function initialize( + address newStateSender, + address newExitHelper, + address newRootERC20Predicate, + address newChildTokenTemplate + ) external virtual initializer { + _initialize(newStateSender, newExitHelper, newRootERC20Predicate, newChildTokenTemplate); + } + + /** + * @notice Function to be used for token deposits + * @param sender Address of the sender on the root chain + * @param data Data sent by the sender + * @dev Can be extended to include other signatures for more functionality + */ + function onL2StateReceive(uint256 /* id */, address sender, bytes calldata data) external { + require(msg.sender == exitHelper, "ChildMintableERC20Predicate: ONLY_STATE_RECEIVER"); + require(sender == rootERC20Predicate, "ChildMintableERC20Predicate: ONLY_ROOT_PREDICATE"); + + if (bytes32(data[:32]) == DEPOSIT_SIG) { + _beforeTokenDeposit(); + _deposit(data[32:]); + _afterTokenDeposit(); + } else if (bytes32(data[:32]) == MAP_TOKEN_SIG) { + _mapToken(data); + } else { + revert("ChildMintableERC20Predicate: INVALID_SIGNATURE"); + } + } + + /** + * @notice Function to withdraw tokens from the withdrawer to themselves on the root chain + * @param childToken Address of the child token being withdrawn + * @param amount Amount to withdraw + */ + function withdraw(IChildERC20 childToken, uint256 amount) external { + _beforeTokenWithdraw(); + _withdraw(childToken, msg.sender, amount); + _afterTokenWithdraw(); + } + + /** + * @notice Function to withdraw tokens from the withdrawer to another address on the root chain + * @param childToken Address of the child token being withdrawn + * @param receiver Address of the receiver on the root chain + * @param amount Amount to withdraw + */ + function withdrawTo(IChildERC20 childToken, address receiver, uint256 amount) external { + _beforeTokenWithdraw(); + _withdraw(childToken, receiver, amount); + _afterTokenWithdraw(); + } + + /** + * @notice Internal initialization function for ChildERC20Predicate + * @param newStateSender Address of L2StateSender to send exit information to + * @param newExitHelper Address of StateReceiver to receive deposit information from + * @param newRootERC20Predicate Address of root ERC20 predicate to communicate with + * @param newChildTokenTemplate Address of child token implementation to deploy clones of + * @dev Can be called multiple times. + */ + function _initialize( + address newStateSender, + address newExitHelper, + address newRootERC20Predicate, + address newChildTokenTemplate + ) internal { + require( + newStateSender != address(0) && + newExitHelper != address(0) && + newRootERC20Predicate != address(0) && + newChildTokenTemplate != address(0), + "ChildMintableERC20Predicate: BAD_INITIALIZATION" + ); + stateSender = IStateSender(newStateSender); + exitHelper = newExitHelper; + rootERC20Predicate = newRootERC20Predicate; + childTokenTemplate = newChildTokenTemplate; + } + + // solhint-disable no-empty-blocks + function _beforeTokenDeposit() internal virtual {} + + // slither-disable-next-line dead-code + function _beforeTokenWithdraw() internal virtual {} + + function _afterTokenDeposit() internal virtual {} + + function _afterTokenWithdraw() internal virtual {} + + function _withdraw(IChildERC20 childToken, address receiver, uint256 amount) private { + require(address(childToken).code.length != 0, "ChildMintableERC20Predicate: NOT_CONTRACT"); + + address rootToken = childToken.rootToken(); + + require(rootTokenToChildToken[rootToken] == address(childToken), "ChildMintableERC20Predicate: UNMAPPED_TOKEN"); + // a mapped token should never have root token unset + assert(rootToken != address(0)); + // a mapped token should never have predicate unset + assert(childToken.predicate() == address(this)); + + require(childToken.burn(msg.sender, amount), "ChildMintableERC20Predicate: BURN_FAILED"); + stateSender.syncState(rootERC20Predicate, abi.encode(WITHDRAW_SIG, rootToken, msg.sender, receiver, amount)); + + // slither-disable-next-line reentrancy-events + emit MintableERC20Withdraw(rootToken, address(childToken), msg.sender, receiver, amount); + } + + function _deposit(bytes calldata data) private { + (address depositToken, address depositor, address receiver, uint256 amount) = abi.decode( + data, + (address, address, address, uint256) + ); + + IChildERC20 childToken = IChildERC20(rootTokenToChildToken[depositToken]); + + require(address(childToken) != address(0), "ChildMintableERC20Predicate: UNMAPPED_TOKEN"); + assert(address(childToken).code.length != 0); + + address rootToken = IChildERC20(childToken).rootToken(); + + // a mapped child token should match deposited token + assert(rootToken == depositToken); + // a mapped token should never have root token unset + assert(rootToken != address(0)); + // a mapped token should never have predicate unset + assert(IChildERC20(childToken).predicate() == address(this)); + + require(IChildERC20(childToken).mint(receiver, amount), "ChildMintableERC20Predicate: MINT_FAILED"); + + // slither-disable-next-line reentrancy-events + emit MintableERC20Deposit(depositToken, address(childToken), depositor, receiver, amount); + } + + /** + * @notice Function to be used for mapping a root token to a child token + * @dev Allows for 1-to-1 mappings for any root token to a child token + */ + function _mapToken(bytes calldata data) private { + (, address rootToken, string memory name, string memory symbol, uint8 decimals) = abi.decode( + data, + (bytes32, address, string, string, uint8) + ); + assert(rootToken != address(0)); // invariant since root predicate performs the same check + assert(rootTokenToChildToken[rootToken] == address(0)); // invariant since root predicate performs the same check + IChildERC20 childToken = IChildERC20( + Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(rootToken))) + ); + rootTokenToChildToken[rootToken] = address(childToken); + childToken.initialize(rootToken, name, symbol, decimals); + + // slither-disable-next-line reentrancy-events + emit MintableTokenMapped(rootToken, address(childToken)); + } +} diff --git a/contracts/root/ChildMintableERC721Predicate.sol b/contracts/root/ChildMintableERC721Predicate.sol new file mode 100644 index 00000000..e1e6065d --- /dev/null +++ b/contracts/root/ChildMintableERC721Predicate.sol @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "../interfaces/root/IChildMintableERC721Predicate.sol"; +import "../interfaces/child/IChildERC721.sol"; +import "../interfaces/IStateSender.sol"; + +/** + @title ChildMintableERC721Predicate + @author Polygon Technology (@QEDK) + @notice Enables mintable ERC721 token deposits and withdrawals across an arbitrary root chain and child chain + */ +// solhint-disable reason-string +contract ChildMintableERC721Predicate is Initializable, IChildMintableERC721Predicate { + IStateSender public stateSender; + address public exitHelper; + address public rootERC721Predicate; + address public childTokenTemplate; + bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); + bytes32 public constant DEPOSIT_BATCH_SIG = keccak256("DEPOSIT_BATCH"); + bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW"); + bytes32 public constant WITHDRAW_BATCH_SIG = keccak256("WITHDRAW_BATCH"); + bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); + + mapping(address => address) public rootTokenToChildToken; + + modifier onlyValidToken(IChildERC721 childToken) { + require(_verifyContract(childToken), "ChildMintableERC721Predicate: NOT_CONTRACT"); + _; + } + + /** + * @notice Initilization function for ChildMintableERC721Predicate + * @param newStateSender Address of StateSender to send exit information to + * @param newExitHelper Address of ExitHelper to receive deposit information from + * @param newRootERC721Predicate Address of root ERC721 predicate to communicate with + * @param newChildTokenTemplate Address of child token implementation to deploy clones of + * @dev Can only be called once. `newNativeTokenRootAddress` should be set to zero where root token does not exist. + */ + function initialize( + address newStateSender, + address newExitHelper, + address newRootERC721Predicate, + address newChildTokenTemplate + ) public virtual initializer { + _initialize(newStateSender, newExitHelper, newRootERC721Predicate, newChildTokenTemplate); + } + + /** + * @notice Function to be used for token deposits + * @param sender Address of the sender on the root chain + * @param data Data sent by the sender + * @dev Can be extended to include other signatures for more functionality + */ + function onL2StateReceive(uint256 /* id */, address sender, bytes calldata data) external { + require(msg.sender == exitHelper, "ChildMintableERC721Predicate: ONLY_EXIT_HELPER"); + require(sender == rootERC721Predicate, "ChildMintableERC721Predicate: ONLY_ROOT_PREDICATE"); + + if (bytes32(data[:32]) == DEPOSIT_SIG) { + _beforeTokenDeposit(); + _deposit(data[32:]); + _afterTokenDeposit(); + } else if (bytes32(data[:32]) == DEPOSIT_BATCH_SIG) { + _beforeTokenDeposit(); + _depositBatch(data); + _afterTokenDeposit(); + } else if (bytes32(data[:32]) == MAP_TOKEN_SIG) { + _mapToken(data); + } else { + revert("ChildMintableERC721Predicate: INVALID_SIGNATURE"); + } + } + + /** + * @notice Function to withdraw tokens from the withdrawer to themselves on the root chain + * @param childToken Address of the child token being withdrawn + * @param tokenId index of the NFT to withdraw + */ + function withdraw(IChildERC721 childToken, uint256 tokenId) external { + _beforeTokenWithdraw(); + _withdraw(childToken, msg.sender, tokenId); + _afterTokenWithdraw(); + } + + /** + * @notice Function to withdraw tokens from the withdrawer to another address on the root chain + * @param childToken Address of the child token being withdrawn + * @param receiver Address of the receiver on the root chain + * @param tokenId index of the NFT to withdraw + */ + function withdrawTo(IChildERC721 childToken, address receiver, uint256 tokenId) external { + _beforeTokenWithdraw(); + _withdraw(childToken, receiver, tokenId); + _afterTokenWithdraw(); + } + + /** + * @notice Function to batch withdraw tokens from the withdrawer to other addresses on the root chain + * @param childToken Address of the child token being withdrawn + * @param receivers Addresses of the receivers on the root chain + * @param tokenIds indices of the NFTs to withdraw + */ + function withdrawBatch( + IChildERC721 childToken, + address[] calldata receivers, + uint256[] calldata tokenIds + ) external { + _beforeTokenWithdraw(); + _withdrawBatch(childToken, receivers, tokenIds); + _afterTokenWithdraw(); + } + + /** + * @notice Initilization function for ChildMintableERC721Predicate + * @param newStateSender Address of StateSender to send exit information to + * @param newExitHelper Address of ExitHelper to receive deposit information from + * @param newRootERC721Predicate Address of root ERC721 predicate to communicate with + * @param newChildTokenTemplate Address of child token implementation to deploy clones of + * @dev Can be called multiple times. + */ + function _initialize( + address newStateSender, + address newExitHelper, + address newRootERC721Predicate, + address newChildTokenTemplate + ) internal virtual { + require( + newStateSender != address(0) && + newExitHelper != address(0) && + newRootERC721Predicate != address(0) && + newChildTokenTemplate != address(0), + "ChildMintableERC721Predicate: BAD_INITIALIZATION" + ); + stateSender = IStateSender(newStateSender); + exitHelper = newExitHelper; + rootERC721Predicate = newRootERC721Predicate; + childTokenTemplate = newChildTokenTemplate; + } + + // solhint-disable no-empty-blocks + // slither-disable-start dead-code + function _beforeTokenDeposit() internal virtual {} + + function _beforeTokenWithdraw() internal virtual {} + + function _afterTokenDeposit() internal virtual {} + + function _afterTokenWithdraw() internal virtual {} + + // slither-disable-end dead-code + + function _withdraw(IChildERC721 childToken, address receiver, uint256 tokenId) private onlyValidToken(childToken) { + address rootToken = childToken.rootToken(); + + require( + rootTokenToChildToken[rootToken] == address(childToken), + "ChildMintableERC721Predicate: UNMAPPED_TOKEN" + ); + // a mapped token should never have root token unset + assert(rootToken != address(0)); + // a mapped token should never have predicate unset + assert(childToken.predicate() == address(this)); + + require(childToken.burn(msg.sender, tokenId), "ChildMintableERC721Predicate: BURN_FAILED"); + stateSender.syncState(rootERC721Predicate, abi.encode(WITHDRAW_SIG, rootToken, msg.sender, receiver, tokenId)); + + // slither-disable-next-line reentrancy-events + emit MintableERC721Withdraw(rootToken, address(childToken), msg.sender, receiver, tokenId); + } + + function _withdrawBatch( + IChildERC721 childToken, + address[] calldata receivers, + uint256[] calldata tokenIds + ) private onlyValidToken(childToken) { + address rootToken = childToken.rootToken(); + + require( + rootTokenToChildToken[rootToken] == address(childToken), + "ChildMintableERC721Predicate: UNMAPPED_TOKEN" + ); + // a mapped token should never have root token unset + assert(rootToken != address(0)); + // a mapped token should never have predicate unset + assert(childToken.predicate() == address(this)); + + require(receivers.length == tokenIds.length, "ChildMintableERC721Predicate: INVALID_LENGTH"); + require(childToken.burnBatch(msg.sender, tokenIds), "ChildMintableERC721Predicate: BURN_FAILED"); + stateSender.syncState( + rootERC721Predicate, + abi.encode(WITHDRAW_BATCH_SIG, rootToken, msg.sender, receivers, tokenIds) + ); + + // slither-disable-next-line reentrancy-events + emit MintableERC721WithdrawBatch(rootToken, address(childToken), msg.sender, receivers, tokenIds); + } + + function _deposit(bytes calldata data) private { + (address depositToken, address depositor, address receiver, uint256 tokenId) = abi.decode( + data, + (address, address, address, uint256) + ); + + IChildERC721 childToken = IChildERC721(rootTokenToChildToken[depositToken]); + + require(address(childToken) != address(0), "ChildMintableERC721Predicate: UNMAPPED_TOKEN"); + // a mapped token should always pass specifications + assert(_verifyContract(childToken)); + + address rootToken = IChildERC721(childToken).rootToken(); + + // a mapped token should match deposited token + assert(rootToken == depositToken); + // a mapped token should never have root token unset + assert(rootToken != address(0)); + // a mapped token should never have predicate unset + assert(IChildERC721(childToken).predicate() == address(this)); + require(IChildERC721(childToken).mint(receiver, tokenId), "ChildMintableERC721Predicate: MINT_FAILED"); + // slither-disable-next-line reentrancy-events + emit MintableERC721Deposit(depositToken, address(childToken), depositor, receiver, tokenId); + } + + function _depositBatch(bytes calldata data) private { + (, address depositToken, address depositor, address[] memory receivers, uint256[] memory tokenIds) = abi.decode( + data, + (bytes32, address, address, address[], uint256[]) + ); + + IChildERC721 childToken = IChildERC721(rootTokenToChildToken[depositToken]); + + require(address(childToken) != address(0), "ChildMintableERC721Predicate: UNMAPPED_TOKEN"); + // a mapped token should always pass specifications + assert(_verifyContract(childToken)); + + address rootToken = IChildERC721(childToken).rootToken(); + + // a mapped token should match deposited token + assert(rootToken == depositToken); + // a mapped token should never have root token unset + assert(rootToken != address(0)); + // a mapped token should never have predicate unset + assert(IChildERC721(childToken).predicate() == address(this)); + require(IChildERC721(childToken).mintBatch(receivers, tokenIds), "ChildMintableERC721Predicate: MINT_FAILED"); + // slither-disable-next-line reentrancy-events + emit MintableERC721DepositBatch(depositToken, address(childToken), depositor, receivers, tokenIds); + } + + /** + * @notice Function to be used for mapping a root token to a child token + * @dev Allows for 1-to-1 mappings for any root token to a child token + */ + function _mapToken(bytes calldata data) private { + (, address rootToken, string memory name, string memory symbol) = abi.decode( + data, + (bytes32, address, string, string) + ); + assert(rootToken != address(0)); // invariant since root predicate performs the same check + assert(rootTokenToChildToken[rootToken] == address(0)); // invariant since root predicate performs the same check + IChildERC721 childToken = IChildERC721( + Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(rootToken))) + ); + rootTokenToChildToken[rootToken] = address(childToken); + childToken.initialize(rootToken, name, symbol); + + // slither-disable-next-line reentrancy-events + emit MintableTokenMapped(rootToken, address(childToken)); + } + + // slither does not handle try-catch blocks correctly + // slither-disable-next-line unused-return + function _verifyContract(IChildERC721 childToken) private view returns (bool) { + if (address(childToken).code.length == 0) { + return false; + } + // slither-disable-next-line uninitialized-local,variable-scope + try childToken.supportsInterface(0x80ac58cd) returns (bool support) { + return support; + } catch { + return false; + } + } +} diff --git a/contracts/root/RootERC1155Predicate.sol b/contracts/root/RootERC1155Predicate.sol index eeb279a2..99eadcb1 100644 --- a/contracts/root/RootERC1155Predicate.sol +++ b/contracts/root/RootERC1155Predicate.sol @@ -5,11 +5,10 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; import "@openzeppelin/contracts/proxy/Clones.sol"; import "../interfaces/root/IRootERC1155Predicate.sol"; -import "../interfaces/root/IL2StateReceiver.sol"; import "../interfaces/IStateSender.sol"; // solhint-disable reason-string -contract RootERC1155Predicate is IRootERC1155Predicate, IL2StateReceiver, Initializable, ERC1155Holder { +contract RootERC1155Predicate is Initializable, ERC1155Holder, IRootERC1155Predicate { IStateSender public stateSender; address public exitHelper; address public childERC1155Predicate; diff --git a/contracts/root/RootERC20Predicate.sol b/contracts/root/RootERC20Predicate.sol index 3a90fb97..fe1bc533 100644 --- a/contracts/root/RootERC20Predicate.sol +++ b/contracts/root/RootERC20Predicate.sol @@ -5,11 +5,10 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/proxy/Clones.sol"; import "../interfaces/root/IRootERC20Predicate.sol"; -import "../interfaces/root/IL2StateReceiver.sol"; import "../interfaces/IStateSender.sol"; // solhint-disable reason-string -contract RootERC20Predicate is IRootERC20Predicate, IL2StateReceiver, Initializable { +contract RootERC20Predicate is Initializable, IRootERC20Predicate { using SafeERC20 for IERC20Metadata; IStateSender public stateSender; diff --git a/contracts/root/RootERC721Predicate.sol b/contracts/root/RootERC721Predicate.sol index 6adba493..714e1116 100644 --- a/contracts/root/RootERC721Predicate.sol +++ b/contracts/root/RootERC721Predicate.sol @@ -5,11 +5,10 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import "@openzeppelin/contracts/proxy/Clones.sol"; import "../interfaces/root/IRootERC721Predicate.sol"; -import "../interfaces/root/IL2StateReceiver.sol"; import "../interfaces/IStateSender.sol"; // solhint-disable reason-string -contract RootERC721Predicate is IRootERC721Predicate, IL2StateReceiver, Initializable, ERC721Holder { +contract RootERC721Predicate is Initializable, ERC721Holder, IRootERC721Predicate { IStateSender public stateSender; address public exitHelper; address public childERC721Predicate; diff --git a/docs/child/RootMintableERC1155Predicate.md b/docs/child/RootMintableERC1155Predicate.md new file mode 100644 index 00000000..56352390 --- /dev/null +++ b/docs/child/RootMintableERC1155Predicate.md @@ -0,0 +1,499 @@ +# RootMintableERC1155Predicate + + + + + + + + + +## Methods + +### DEPOSIT_BATCH_SIG + +```solidity +function DEPOSIT_BATCH_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### DEPOSIT_SIG + +```solidity +function DEPOSIT_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### MAP_TOKEN_SIG + +```solidity +function MAP_TOKEN_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### WITHDRAW_BATCH_SIG + +```solidity +function WITHDRAW_BATCH_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### WITHDRAW_SIG + +```solidity +function WITHDRAW_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### childERC1155Predicate + +```solidity +function childERC1155Predicate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### childTokenTemplate + +```solidity +function childTokenTemplate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### deposit + +```solidity +function deposit(contract IERC1155MetadataURI rootToken, uint256 tokenId, uint256 amount) external nonpayable +``` + +Function to deposit tokens from the depositor to themselves on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC1155MetadataURI | Address of the root token being deposited | +| tokenId | uint256 | Index of the NFT to deposit | +| amount | uint256 | Amount to deposit | + +### depositBatch + +```solidity +function depositBatch(contract IERC1155MetadataURI rootToken, address[] receivers, uint256[] tokenIds, uint256[] amounts) external nonpayable +``` + +Function to deposit tokens from the depositor to other addresses on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC1155MetadataURI | Address of the root token being deposited | +| receivers | address[] | Addresses of the receivers on the child chain | +| tokenIds | uint256[] | Indeices of the NFTs to deposit | +| amounts | uint256[] | Amounts to deposit | + +### depositTo + +```solidity +function depositTo(contract IERC1155MetadataURI rootToken, address receiver, uint256 tokenId, uint256 amount) external nonpayable +``` + +Function to deposit tokens from the depositor to another address on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC1155MetadataURI | Address of the root token being deposited | +| receiver | address | undefined | +| tokenId | uint256 | Index of the NFT to deposit | +| amount | uint256 | Amount to deposit | + +### initialize + +```solidity +function initialize(address newL2StateSender, address newStateReceiver, address newChildERC1155Predicate, address newChildTokenTemplate) external nonpayable +``` + +Initilization function for RootERC1155Predicate + +*Can only be called once.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| newL2StateSender | address | Address of StateSender to send deposit information to | +| newStateReceiver | address | Address of ExitHelper to receive withdrawal information from | +| newChildERC1155Predicate | address | Address of child ERC1155 predicate to communicate with | +| newChildTokenTemplate | address | undefined | + +### l2StateSender + +```solidity +function l2StateSender() external view returns (contract IStateSender) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | contract IStateSender | undefined | + +### mapToken + +```solidity +function mapToken(contract IERC1155MetadataURI rootToken) external nonpayable returns (address childToken) +``` + +Function to be used for token mapping + +*Called internally on deposit if token is not mapped already* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC1155MetadataURI | Address of the root token to map | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| childToken | address | Address of the mapped child token | + +### onERC1155BatchReceived + +```solidity +function onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) external nonpayable returns (bytes4) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | +| _1 | address | undefined | +| _2 | uint256[] | undefined | +| _3 | uint256[] | undefined | +| _4 | bytes | undefined | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes4 | undefined | + +### onERC1155Received + +```solidity +function onERC1155Received(address, address, uint256, uint256, bytes) external nonpayable returns (bytes4) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | +| _1 | address | undefined | +| _2 | uint256 | undefined | +| _3 | uint256 | undefined | +| _4 | bytes | undefined | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes4 | undefined | + +### onStateReceive + +```solidity +function onStateReceive(uint256, address sender, bytes data) external nonpayable +``` + +Function to be used for token withdrawals + +*Can be extended to include other signatures for more functionality* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | +| sender | address | undefined | +| data | bytes | undefined | + +### rootTokenToChildToken + +```solidity +function rootTokenToChildToken(address) external view returns (address) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### stateReceiver + +```solidity +function stateReceiver() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### supportsInterface + +```solidity +function supportsInterface(bytes4 interfaceId) external view returns (bool) +``` + + + +*See {IERC165-supportsInterface}.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| interfaceId | bytes4 | undefined | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bool | undefined | + + + +## Events + +### Initialized + +```solidity +event Initialized(uint8 version) +``` + + + +*Triggered when the contract has been initialized or reinitialized.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| version | uint8 | undefined | + +### L2MintableERC1155Deposit + +```solidity +event L2MintableERC1155Deposit(address indexed rootToken, address indexed childToken, address depositor, address indexed receiver, uint256 tokenId, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| depositor | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | +| amount | uint256 | undefined | + +### L2MintableERC1155DepositBatch + +```solidity +event L2MintableERC1155DepositBatch(address indexed rootToken, address indexed childToken, address indexed depositor, address[] receivers, uint256[] tokenIds, uint256[] amounts) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| depositor `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | +| amounts | uint256[] | undefined | + +### L2MintableERC1155Withdraw + +```solidity +event L2MintableERC1155Withdraw(address indexed rootToken, address indexed childToken, address withdrawer, address indexed receiver, uint256 tokenId, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| withdrawer | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | +| amount | uint256 | undefined | + +### L2MintableERC1155WithdrawBatch + +```solidity +event L2MintableERC1155WithdrawBatch(address indexed rootToken, address indexed childToken, address indexed withdrawer, address[] receivers, uint256[] tokenIds, uint256[] amounts) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| withdrawer `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | +| amounts | uint256[] | undefined | + +### L2MintableTokenMapped + +```solidity +event L2MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + diff --git a/docs/child/RootMintableERC20Predicate.md b/docs/child/RootMintableERC20Predicate.md new file mode 100644 index 00000000..7f33cb94 --- /dev/null +++ b/docs/child/RootMintableERC20Predicate.md @@ -0,0 +1,498 @@ +# RootMintableERC20Predicate + + + + + + + + + +## Methods + +### ALLOWLIST_PRECOMPILE + +```solidity +function ALLOWLIST_PRECOMPILE() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### BLOCKLIST_PRECOMPILE + +```solidity +function BLOCKLIST_PRECOMPILE() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### DEPOSIT_SIG + +```solidity +function DEPOSIT_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### MAP_TOKEN_SIG + +```solidity +function MAP_TOKEN_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### NATIVE_TOKEN_CONTRACT + +```solidity +function NATIVE_TOKEN_CONTRACT() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### NATIVE_TRANSFER_PRECOMPILE + +```solidity +function NATIVE_TRANSFER_PRECOMPILE() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### NATIVE_TRANSFER_PRECOMPILE_GAS + +```solidity +function NATIVE_TRANSFER_PRECOMPILE_GAS() external view returns (uint256) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | + +### READ_ADDRESSLIST_GAS + +```solidity +function READ_ADDRESSLIST_GAS() external view returns (uint256) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | + +### SYSTEM + +```solidity +function SYSTEM() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### VALIDATOR_PKCHECK_PRECOMPILE + +```solidity +function VALIDATOR_PKCHECK_PRECOMPILE() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### VALIDATOR_PKCHECK_PRECOMPILE_GAS + +```solidity +function VALIDATOR_PKCHECK_PRECOMPILE_GAS() external view returns (uint256) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | + +### WITHDRAW_SIG + +```solidity +function WITHDRAW_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### childERC20Predicate + +```solidity +function childERC20Predicate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### childTokenTemplate + +```solidity +function childTokenTemplate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### deposit + +```solidity +function deposit(contract IERC20Metadata rootToken, uint256 amount) external nonpayable +``` + +Function to deposit tokens from the depositor to themselves on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC20Metadata | Address of the root token being deposited | +| amount | uint256 | Amount to deposit | + +### depositTo + +```solidity +function depositTo(contract IERC20Metadata rootToken, address receiver, uint256 amount) external nonpayable +``` + +Function to deposit tokens from the depositor to another address on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC20Metadata | Address of the root token being deposited | +| receiver | address | undefined | +| amount | uint256 | Amount to deposit | + +### initialize + +```solidity +function initialize(address newL2StateSender, address newStateReceiver, address newChildERC20Predicate, address newChildTokenTemplate) external nonpayable +``` + +Initilization function for RootMintableERC20Predicate + +*Can only be called once.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| newL2StateSender | address | Address of L2StateSender to send exit information to | +| newStateReceiver | address | Address of StateReceiver to receive deposit information from | +| newChildERC20Predicate | address | Address of root ERC20 predicate to communicate with | +| newChildTokenTemplate | address | Address of child token implementation to deploy clones of | + +### l2StateSender + +```solidity +function l2StateSender() external view returns (contract IStateSender) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | contract IStateSender | undefined | + +### mapToken + +```solidity +function mapToken(contract IERC20Metadata rootToken) external nonpayable returns (address) +``` + +Function to be used for token mapping + +*Called internally on deposit if token is not mapped already* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC20Metadata | Address of the root token to map | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | address Address of the child token | + +### onStateReceive + +```solidity +function onStateReceive(uint256, address sender, bytes data) external nonpayable +``` + +Function to be used for token withdrawals + +*Can be extended to include other signatures for more functionality* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | +| sender | address | Address of the sender on the root chain | +| data | bytes | Data sent by the sender | + +### rootTokenToChildToken + +```solidity +function rootTokenToChildToken(address) external view returns (address) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### stateReceiver + +```solidity +function stateReceiver() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + + + +## Events + +### Initialized + +```solidity +event Initialized(uint8 version) +``` + + + +*Triggered when the contract has been initialized or reinitialized.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| version | uint8 | undefined | + +### L2MintableERC20Deposit + +```solidity +event L2MintableERC20Deposit(address indexed rootToken, address indexed childToken, address depositor, address indexed receiver, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| depositor | address | undefined | +| receiver `indexed` | address | undefined | +| amount | uint256 | undefined | + +### L2MintableERC20Withdraw + +```solidity +event L2MintableERC20Withdraw(address indexed rootToken, address indexed childToken, address withdrawer, address indexed receiver, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| withdrawer | address | undefined | +| receiver `indexed` | address | undefined | +| amount | uint256 | undefined | + +### L2MintableTokenMapped + +```solidity +event L2MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + +## Errors + +### Unauthorized + +```solidity +error Unauthorized(string only) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| only | string | undefined | + + diff --git a/docs/child/RootMintableERC721Predicate.md b/docs/child/RootMintableERC721Predicate.md new file mode 100644 index 00000000..f85d2910 --- /dev/null +++ b/docs/child/RootMintableERC721Predicate.md @@ -0,0 +1,615 @@ +# RootMintableERC721Predicate + + + + + + + + + +## Methods + +### ALLOWLIST_PRECOMPILE + +```solidity +function ALLOWLIST_PRECOMPILE() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### BLOCKLIST_PRECOMPILE + +```solidity +function BLOCKLIST_PRECOMPILE() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### DEPOSIT_BATCH_SIG + +```solidity +function DEPOSIT_BATCH_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### DEPOSIT_SIG + +```solidity +function DEPOSIT_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### MAP_TOKEN_SIG + +```solidity +function MAP_TOKEN_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### NATIVE_TOKEN_CONTRACT + +```solidity +function NATIVE_TOKEN_CONTRACT() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### NATIVE_TRANSFER_PRECOMPILE + +```solidity +function NATIVE_TRANSFER_PRECOMPILE() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### NATIVE_TRANSFER_PRECOMPILE_GAS + +```solidity +function NATIVE_TRANSFER_PRECOMPILE_GAS() external view returns (uint256) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | + +### READ_ADDRESSLIST_GAS + +```solidity +function READ_ADDRESSLIST_GAS() external view returns (uint256) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | + +### SYSTEM + +```solidity +function SYSTEM() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### VALIDATOR_PKCHECK_PRECOMPILE + +```solidity +function VALIDATOR_PKCHECK_PRECOMPILE() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### VALIDATOR_PKCHECK_PRECOMPILE_GAS + +```solidity +function VALIDATOR_PKCHECK_PRECOMPILE_GAS() external view returns (uint256) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | + +### WITHDRAW_BATCH_SIG + +```solidity +function WITHDRAW_BATCH_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### WITHDRAW_SIG + +```solidity +function WITHDRAW_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### childERC721Predicate + +```solidity +function childERC721Predicate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### childTokenTemplate + +```solidity +function childTokenTemplate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### deposit + +```solidity +function deposit(contract IERC721Metadata rootToken, uint256 tokenId) external nonpayable +``` + +Function to deposit tokens from the depositor to themselves on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC721Metadata | Address of the root token being deposited | +| tokenId | uint256 | Index of the NFT to deposit | + +### depositBatch + +```solidity +function depositBatch(contract IERC721Metadata rootToken, address[] receivers, uint256[] tokenIds) external nonpayable +``` + +Function to deposit tokens from the depositor to other addresses on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC721Metadata | Address of the root token being deposited | +| receivers | address[] | Addresses of the receivers on the child chain | +| tokenIds | uint256[] | Indeices of the NFTs to deposit | + +### depositTo + +```solidity +function depositTo(contract IERC721Metadata rootToken, address receiver, uint256 tokenId) external nonpayable +``` + +Function to deposit tokens from the depositor to another address on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC721Metadata | Address of the root token being deposited | +| receiver | address | undefined | +| tokenId | uint256 | Index of the NFT to deposit | + +### initialize + +```solidity +function initialize(address newL2StateSender, address newStateReceiver, address newChildERC721Predicate, address newChildTokenTemplate) external nonpayable +``` + +Initilization function for RootMintableERC721Predicate + +*Can only be called once.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| newL2StateSender | address | Address of StateSender to send deposit information to | +| newStateReceiver | address | Address of ExitHelper to receive withdrawal information from | +| newChildERC721Predicate | address | Address of child ERC721 predicate to communicate with | +| newChildTokenTemplate | address | Address of child token template to calculate child token addresses | + +### l2StateSender + +```solidity +function l2StateSender() external view returns (contract IStateSender) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | contract IStateSender | undefined | + +### mapToken + +```solidity +function mapToken(contract IERC721Metadata rootToken) external nonpayable returns (address) +``` + +Function to be used for token mapping + +*Called internally on deposit if token is not mapped already* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC721Metadata | Address of the root token to map | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | Address of the mapped child token | + +### onERC721Received + +```solidity +function onERC721Received(address, address, uint256, bytes) external nonpayable returns (bytes4) +``` + + + +*See {IERC721Receiver-onERC721Received}. Always returns `IERC721Receiver.onERC721Received.selector`.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | +| _1 | address | undefined | +| _2 | uint256 | undefined | +| _3 | bytes | undefined | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes4 | undefined | + +### onStateReceive + +```solidity +function onStateReceive(uint256, address sender, bytes data) external nonpayable +``` + +Function to be used for token withdrawals + +*Can be extended to include other signatures for more functionality* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | +| sender | address | undefined | +| data | bytes | undefined | + +### rootTokenToChildToken + +```solidity +function rootTokenToChildToken(address) external view returns (address) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### stateReceiver + +```solidity +function stateReceiver() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + + + +## Events + +### Initialized + +```solidity +event Initialized(uint8 version) +``` + + + +*Triggered when the contract has been initialized or reinitialized.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| version | uint8 | undefined | + +### L2MintableERC721Deposit + +```solidity +event L2MintableERC721Deposit(address indexed rootToken, address indexed childToken, address depositor, address indexed receiver, uint256 tokenId) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| depositor | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | + +### L2MintableERC721DepositBatch + +```solidity +event L2MintableERC721DepositBatch(address indexed rootToken, address indexed childToken, address indexed depositor, address[] receivers, uint256[] tokenIds) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| depositor `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | + +### L2MintableERC721Withdraw + +```solidity +event L2MintableERC721Withdraw(address indexed rootToken, address indexed childToken, address withdrawer, address indexed receiver, uint256 tokenId) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| withdrawer | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | + +### L2MintableERC721WithdrawBatch + +```solidity +event L2MintableERC721WithdrawBatch(address indexed rootToken, address indexed childToken, address indexed withdrawer, address[] receivers, uint256[] tokenIds) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| withdrawer `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | + +### L2MintableTokenMapped + +```solidity +event L2MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + +## Errors + +### Unauthorized + +```solidity +error Unauthorized(string only) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| only | string | undefined | + + diff --git a/docs/interfaces/child/IRootMintableERC1155Predicate.md b/docs/interfaces/child/IRootMintableERC1155Predicate.md new file mode 100644 index 00000000..dbcf185e --- /dev/null +++ b/docs/interfaces/child/IRootMintableERC1155Predicate.md @@ -0,0 +1,215 @@ +# IRootMintableERC1155Predicate + + + + + + + + + +## Methods + +### deposit + +```solidity +function deposit(contract IERC1155MetadataURI rootToken, uint256 tokenId, uint256 amount) external nonpayable +``` + +Function to deposit tokens from the depositor to themselves on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC1155MetadataURI | Address of the root token being deposited | +| tokenId | uint256 | Index of the NFT to deposit | +| amount | uint256 | Amount to deposit | + +### depositBatch + +```solidity +function depositBatch(contract IERC1155MetadataURI rootToken, address[] receivers, uint256[] tokenIds, uint256[] amounts) external nonpayable +``` + +Function to deposit tokens from the depositor to other addresses on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC1155MetadataURI | Address of the root token being deposited | +| receivers | address[] | Addresses of the receivers on the child chain | +| tokenIds | uint256[] | Indeices of the NFTs to deposit | +| amounts | uint256[] | Amounts to deposit | + +### depositTo + +```solidity +function depositTo(contract IERC1155MetadataURI rootToken, address receiver, uint256 tokenId, uint256 amount) external nonpayable +``` + +Function to deposit tokens from the depositor to another address on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC1155MetadataURI | Address of the root token being deposited | +| receiver | address | undefined | +| tokenId | uint256 | Index of the NFT to deposit | +| amount | uint256 | Amount to deposit | + +### mapToken + +```solidity +function mapToken(contract IERC1155MetadataURI rootToken) external nonpayable returns (address childToken) +``` + +Function to be used for token mapping + +*Called internally on deposit if token is not mapped already* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC1155MetadataURI | Address of the root token to map | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| childToken | address | Address of the mapped child token | + +### onStateReceive + +```solidity +function onStateReceive(uint256 counter, address sender, bytes data) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| counter | uint256 | undefined | +| sender | address | undefined | +| data | bytes | undefined | + + + +## Events + +### L2MintableERC1155Deposit + +```solidity +event L2MintableERC1155Deposit(address indexed rootToken, address indexed childToken, address depositor, address indexed receiver, uint256 tokenId, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| depositor | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | +| amount | uint256 | undefined | + +### L2MintableERC1155DepositBatch + +```solidity +event L2MintableERC1155DepositBatch(address indexed rootToken, address indexed childToken, address indexed depositor, address[] receivers, uint256[] tokenIds, uint256[] amounts) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| depositor `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | +| amounts | uint256[] | undefined | + +### L2MintableERC1155Withdraw + +```solidity +event L2MintableERC1155Withdraw(address indexed rootToken, address indexed childToken, address withdrawer, address indexed receiver, uint256 tokenId, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| withdrawer | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | +| amount | uint256 | undefined | + +### L2MintableERC1155WithdrawBatch + +```solidity +event L2MintableERC1155WithdrawBatch(address indexed rootToken, address indexed childToken, address indexed withdrawer, address[] receivers, uint256[] tokenIds, uint256[] amounts) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| withdrawer `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | +| amounts | uint256[] | undefined | + +### L2MintableTokenMapped + +```solidity +event L2MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + diff --git a/docs/interfaces/child/IRootMintableERC20Predicate.md b/docs/interfaces/child/IRootMintableERC20Predicate.md new file mode 100644 index 00000000..8c272aff --- /dev/null +++ b/docs/interfaces/child/IRootMintableERC20Predicate.md @@ -0,0 +1,150 @@ +# IRootMintableERC20Predicate + + + + + + + + + +## Methods + +### deposit + +```solidity +function deposit(contract IERC20Metadata rootToken, uint256 amount) external nonpayable +``` + +Function to deposit tokens from the depositor to themselves on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC20Metadata | Address of the root token being deposited | +| amount | uint256 | Amount to deposit | + +### depositTo + +```solidity +function depositTo(contract IERC20Metadata rootToken, address receiver, uint256 amount) external nonpayable +``` + +Function to deposit tokens from the depositor to another address on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC20Metadata | Address of the root token being deposited | +| receiver | address | undefined | +| amount | uint256 | Amount to deposit | + +### mapToken + +```solidity +function mapToken(contract IERC20Metadata rootToken) external nonpayable returns (address) +``` + +Function to be used for token mapping + +*Called internally on deposit if token is not mapped already* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC20Metadata | Address of the root token to map | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | address Address of the child token | + +### onStateReceive + +```solidity +function onStateReceive(uint256 counter, address sender, bytes data) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| counter | uint256 | undefined | +| sender | address | undefined | +| data | bytes | undefined | + + + +## Events + +### L2MintableERC20Deposit + +```solidity +event L2MintableERC20Deposit(address indexed rootToken, address indexed childToken, address depositor, address indexed receiver, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| depositor | address | undefined | +| receiver `indexed` | address | undefined | +| amount | uint256 | undefined | + +### L2MintableERC20Withdraw + +```solidity +event L2MintableERC20Withdraw(address indexed rootToken, address indexed childToken, address withdrawer, address indexed receiver, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| withdrawer | address | undefined | +| receiver `indexed` | address | undefined | +| amount | uint256 | undefined | + +### L2MintableTokenMapped + +```solidity +event L2MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + diff --git a/docs/interfaces/child/IRootMintableERC721Predicate.md b/docs/interfaces/child/IRootMintableERC721Predicate.md new file mode 100644 index 00000000..de8ee2b2 --- /dev/null +++ b/docs/interfaces/child/IRootMintableERC721Predicate.md @@ -0,0 +1,208 @@ +# IRootMintableERC721Predicate + + + + + + + + + +## Methods + +### deposit + +```solidity +function deposit(contract IERC721Metadata rootToken, uint256 tokenId) external nonpayable +``` + +Function to deposit tokens from the depositor to themselves on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC721Metadata | Address of the root token being deposited | +| tokenId | uint256 | Index of the NFT to deposit | + +### depositBatch + +```solidity +function depositBatch(contract IERC721Metadata rootToken, address[] receivers, uint256[] tokenIds) external nonpayable +``` + +Function to deposit tokens from the depositor to other addresses on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC721Metadata | Address of the root token being deposited | +| receivers | address[] | Addresses of the receivers on the child chain | +| tokenIds | uint256[] | Indeices of the NFTs to deposit | + +### depositTo + +```solidity +function depositTo(contract IERC721Metadata rootToken, address receiver, uint256 tokenId) external nonpayable +``` + +Function to deposit tokens from the depositor to another address on the child chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC721Metadata | Address of the root token being deposited | +| receiver | address | undefined | +| tokenId | uint256 | Index of the NFT to deposit | + +### mapToken + +```solidity +function mapToken(contract IERC721Metadata rootToken) external nonpayable returns (address childToken) +``` + +Function to be used for token mapping + +*Called internally on deposit if token is not mapped already* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken | contract IERC721Metadata | Address of the root token to map | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| childToken | address | Address of the mapped child token | + +### onStateReceive + +```solidity +function onStateReceive(uint256 counter, address sender, bytes data) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| counter | uint256 | undefined | +| sender | address | undefined | +| data | bytes | undefined | + + + +## Events + +### L2MintableERC721Deposit + +```solidity +event L2MintableERC721Deposit(address indexed rootToken, address indexed childToken, address depositor, address indexed receiver, uint256 tokenId) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| depositor | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | + +### L2MintableERC721DepositBatch + +```solidity +event L2MintableERC721DepositBatch(address indexed rootToken, address indexed childToken, address indexed depositor, address[] receivers, uint256[] tokenIds) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| depositor `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | + +### L2MintableERC721Withdraw + +```solidity +event L2MintableERC721Withdraw(address indexed rootToken, address indexed childToken, address withdrawer, address indexed receiver, uint256 tokenId) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| withdrawer | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | + +### L2MintableERC721WithdrawBatch + +```solidity +event L2MintableERC721WithdrawBatch(address indexed rootToken, address indexed childToken, address indexed withdrawer, address[] receivers, uint256[] tokenIds) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| withdrawer `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | + +### L2MintableTokenMapped + +```solidity +event L2MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + diff --git a/docs/interfaces/root/IChildMintableERC1155Predicate.md b/docs/interfaces/root/IChildMintableERC1155Predicate.md new file mode 100644 index 00000000..ea422b35 --- /dev/null +++ b/docs/interfaces/root/IChildMintableERC1155Predicate.md @@ -0,0 +1,193 @@ +# IChildMintableERC1155Predicate + + + + + + + + + +## Methods + +### initialize + +```solidity +function initialize(address newStateSender, address newExitHelper, address newRootERC721Predicate, address newChildTokenTemplate) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| newStateSender | address | undefined | +| newExitHelper | address | undefined | +| newRootERC721Predicate | address | undefined | +| newChildTokenTemplate | address | undefined | + +### onL2StateReceive + +```solidity +function onL2StateReceive(uint256 id, address sender, bytes data) external nonpayable +``` + +Called by exit helper when state is received from L2 + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| id | uint256 | undefined | +| sender | address | Address of the sender on the child chain | +| data | bytes | Data sent by the sender | + +### withdraw + +```solidity +function withdraw(contract IChildERC1155 childToken, uint256 tokenId, uint256 amount) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC1155 | undefined | +| tokenId | uint256 | undefined | +| amount | uint256 | undefined | + +### withdrawTo + +```solidity +function withdrawTo(contract IChildERC1155 childToken, address receiver, uint256 tokenId, uint256 amount) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC1155 | undefined | +| receiver | address | undefined | +| tokenId | uint256 | undefined | +| amount | uint256 | undefined | + + + +## Events + +### MintableERC1155Deposit + +```solidity +event MintableERC1155Deposit(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 tokenId, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | +| amount | uint256 | undefined | + +### MintableERC1155DepositBatch + +```solidity +event MintableERC1155DepositBatch(address indexed rootToken, address indexed childToken, address indexed sender, address[] receivers, uint256[] tokenIds, uint256[] amounts) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | +| amounts | uint256[] | undefined | + +### MintableERC1155Withdraw + +```solidity +event MintableERC1155Withdraw(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 tokenId, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | +| amount | uint256 | undefined | + +### MintableERC1155WithdrawBatch + +```solidity +event MintableERC1155WithdrawBatch(address indexed rootToken, address indexed childToken, address indexed sender, address[] receivers, uint256[] tokenIds, uint256[] amounts) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | +| amounts | uint256[] | undefined | + +### MintableTokenMapped + +```solidity +event MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + diff --git a/docs/interfaces/root/IChildMintableERC20Predicate.md b/docs/interfaces/root/IChildMintableERC20Predicate.md new file mode 100644 index 00000000..641092b6 --- /dev/null +++ b/docs/interfaces/root/IChildMintableERC20Predicate.md @@ -0,0 +1,147 @@ +# IChildMintableERC20Predicate + + + + + + + + + +## Methods + +### initialize + +```solidity +function initialize(address newL2StateSender, address newStateReceiver, address newRootERC20Predicate, address newChildTokenTemplate) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| newL2StateSender | address | undefined | +| newStateReceiver | address | undefined | +| newRootERC20Predicate | address | undefined | +| newChildTokenTemplate | address | undefined | + +### onL2StateReceive + +```solidity +function onL2StateReceive(uint256 id, address sender, bytes data) external nonpayable +``` + +Called by exit helper when state is received from L2 + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| id | uint256 | undefined | +| sender | address | Address of the sender on the child chain | +| data | bytes | Data sent by the sender | + +### withdraw + +```solidity +function withdraw(contract IChildERC20 childToken, uint256 amount) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC20 | undefined | +| amount | uint256 | undefined | + +### withdrawTo + +```solidity +function withdrawTo(contract IChildERC20 childToken, address receiver, uint256 amount) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC20 | undefined | +| receiver | address | undefined | +| amount | uint256 | undefined | + + + +## Events + +### MintableERC20Deposit + +```solidity +event MintableERC20Deposit(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| amount | uint256 | undefined | + +### MintableERC20Withdraw + +```solidity +event MintableERC20Withdraw(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| amount | uint256 | undefined | + +### MintableTokenMapped + +```solidity +event MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + diff --git a/docs/interfaces/root/IChildMintableERC721Predicate.md b/docs/interfaces/root/IChildMintableERC721Predicate.md new file mode 100644 index 00000000..311027cc --- /dev/null +++ b/docs/interfaces/root/IChildMintableERC721Predicate.md @@ -0,0 +1,205 @@ +# IChildMintableERC721Predicate + + + + + + + + + +## Methods + +### initialize + +```solidity +function initialize(address newStateSender, address newExitHelper, address newRootERC721Predicate, address newChildTokenTemplate) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| newStateSender | address | undefined | +| newExitHelper | address | undefined | +| newRootERC721Predicate | address | undefined | +| newChildTokenTemplate | address | undefined | + +### onL2StateReceive + +```solidity +function onL2StateReceive(uint256, address sender, bytes data) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | +| sender | address | undefined | +| data | bytes | undefined | + +### withdraw + +```solidity +function withdraw(contract IChildERC721 childToken, uint256 tokenId) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC721 | undefined | +| tokenId | uint256 | undefined | + +### withdrawBatch + +```solidity +function withdrawBatch(contract IChildERC721 childToken, address[] receivers, uint256[] tokenIds) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC721 | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | + +### withdrawTo + +```solidity +function withdrawTo(contract IChildERC721 childToken, address receiver, uint256 tokenId) external nonpayable +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC721 | undefined | +| receiver | address | undefined | +| tokenId | uint256 | undefined | + + + +## Events + +### MintableERC721Deposit + +```solidity +event MintableERC721Deposit(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 tokenId) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | + +### MintableERC721DepositBatch + +```solidity +event MintableERC721DepositBatch(address indexed rootToken, address indexed childToken, address indexed sender, address[] receivers, uint256[] tokenIds) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | + +### MintableERC721Withdraw + +```solidity +event MintableERC721Withdraw(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 tokenId) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | + +### MintableERC721WithdrawBatch + +```solidity +event MintableERC721WithdrawBatch(address indexed rootToken, address indexed childToken, address indexed sender, address[] receivers, uint256[] tokenIds) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | + +### MintableTokenMapped + +```solidity +event MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + diff --git a/docs/interfaces/root/IRootERC1155Predicate.md b/docs/interfaces/root/IRootERC1155Predicate.md index 8cfe615e..322dc19f 100644 --- a/docs/interfaces/root/IRootERC1155Predicate.md +++ b/docs/interfaces/root/IRootERC1155Predicate.md @@ -88,6 +88,24 @@ Function to be used for token mapping |---|---|---| | childToken | address | Address of the mapped child token | +### onL2StateReceive + +```solidity +function onL2StateReceive(uint256 id, address sender, bytes data) external nonpayable +``` + +Called by exit helper when state is received from L2 + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| id | uint256 | undefined | +| sender | address | Address of the sender on the child chain | +| data | bytes | Data sent by the sender | + ## Events diff --git a/docs/interfaces/root/IRootERC20Predicate.md b/docs/interfaces/root/IRootERC20Predicate.md index 8a538644..c11982d6 100644 --- a/docs/interfaces/root/IRootERC20Predicate.md +++ b/docs/interfaces/root/IRootERC20Predicate.md @@ -67,6 +67,24 @@ Function to be used for token mapping |---|---|---| | _0 | address | address Address of the child token | +### onL2StateReceive + +```solidity +function onL2StateReceive(uint256 id, address sender, bytes data) external nonpayable +``` + +Called by exit helper when state is received from L2 + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| id | uint256 | undefined | +| sender | address | Address of the sender on the child chain | +| data | bytes | Data sent by the sender | + ## Events diff --git a/docs/interfaces/root/IRootERC721Predicate.md b/docs/interfaces/root/IRootERC721Predicate.md index 10265600..61c9ec76 100644 --- a/docs/interfaces/root/IRootERC721Predicate.md +++ b/docs/interfaces/root/IRootERC721Predicate.md @@ -85,6 +85,24 @@ Function to be used for token mapping |---|---|---| | childToken | address | Address of the mapped child token | +### onL2StateReceive + +```solidity +function onL2StateReceive(uint256 id, address sender, bytes data) external nonpayable +``` + +Called by exit helper when state is received from L2 + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| id | uint256 | undefined | +| sender | address | Address of the sender on the child chain | +| data | bytes | Data sent by the sender | + ## Events diff --git a/docs/root/ChildMintableERC1155Predicate.md b/docs/root/ChildMintableERC1155Predicate.md new file mode 100644 index 00000000..4eacef7f --- /dev/null +++ b/docs/root/ChildMintableERC1155Predicate.md @@ -0,0 +1,403 @@ +# ChildMintableERC1155Predicate + +*Polygon Technology (@QEDK)* + +> ChildMintableERC1155Predicate + +Enables mintable ERC1155 token deposits and withdrawals across an arbitrary root chain and child chain + + + +## Methods + +### DEPOSIT_BATCH_SIG + +```solidity +function DEPOSIT_BATCH_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### DEPOSIT_SIG + +```solidity +function DEPOSIT_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### MAP_TOKEN_SIG + +```solidity +function MAP_TOKEN_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### WITHDRAW_BATCH_SIG + +```solidity +function WITHDRAW_BATCH_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### WITHDRAW_SIG + +```solidity +function WITHDRAW_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### childTokenTemplate + +```solidity +function childTokenTemplate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### exitHelper + +```solidity +function exitHelper() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### initialize + +```solidity +function initialize(address newStateSender, address newExitHelper, address newRootERC1155Predicate, address newChildTokenTemplate) external nonpayable +``` + +Initilization function for ChildERC1155Predicate + +*Can only be called once.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| newStateSender | address | Address of L2StateSender to send exit information to | +| newExitHelper | address | Address of StateReceiver to receive deposit information from | +| newRootERC1155Predicate | address | Address of root ERC1155 predicate to communicate with | +| newChildTokenTemplate | address | Address of child token implementation to deploy clones of | + +### onL2StateReceive + +```solidity +function onL2StateReceive(uint256, address sender, bytes data) external nonpayable +``` + +Function to be used for token deposits + +*Can be extended to include other signatures for more functionality* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | +| sender | address | Address of the sender on the root chain | +| data | bytes | Data sent by the sender | + +### rootERC1155Predicate + +```solidity +function rootERC1155Predicate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### rootTokenToChildToken + +```solidity +function rootTokenToChildToken(address) external view returns (address) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### stateSender + +```solidity +function stateSender() external view returns (contract IStateSender) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | contract IStateSender | undefined | + +### withdraw + +```solidity +function withdraw(contract IChildERC1155 childToken, uint256 tokenId, uint256 amount) external nonpayable +``` + +Function to withdraw tokens from the withdrawer to themselves on the root chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC1155 | Address of the child token being withdrawn | +| tokenId | uint256 | Index of the NFT to withdraw | +| amount | uint256 | Amount of the NFT to withdraw | + +### withdrawBatch + +```solidity +function withdrawBatch(contract IChildERC1155 childToken, address[] receivers, uint256[] tokenIds, uint256[] amounts) external nonpayable +``` + +Function to batch withdraw tokens from the withdrawer to other addresses on the root chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC1155 | Address of the child token being withdrawn | +| receivers | address[] | Addresses of the receivers on the root chain | +| tokenIds | uint256[] | indices of the NFTs to withdraw | +| amounts | uint256[] | Amounts of NFTs to withdraw | + +### withdrawTo + +```solidity +function withdrawTo(contract IChildERC1155 childToken, address receiver, uint256 tokenId, uint256 amount) external nonpayable +``` + +Function to withdraw tokens from the withdrawer to another address on the root chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC1155 | Address of the child token being withdrawn | +| receiver | address | Address of the receiver on the root chain | +| tokenId | uint256 | Index of the NFT to withdraw | +| amount | uint256 | Amount of NFT to withdraw | + + + +## Events + +### Initialized + +```solidity +event Initialized(uint8 version) +``` + + + +*Triggered when the contract has been initialized or reinitialized.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| version | uint8 | undefined | + +### MintableERC1155Deposit + +```solidity +event MintableERC1155Deposit(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 tokenId, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | +| amount | uint256 | undefined | + +### MintableERC1155DepositBatch + +```solidity +event MintableERC1155DepositBatch(address indexed rootToken, address indexed childToken, address indexed sender, address[] receivers, uint256[] tokenIds, uint256[] amounts) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | +| amounts | uint256[] | undefined | + +### MintableERC1155Withdraw + +```solidity +event MintableERC1155Withdraw(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 tokenId, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | +| amount | uint256 | undefined | + +### MintableERC1155WithdrawBatch + +```solidity +event MintableERC1155WithdrawBatch(address indexed rootToken, address indexed childToken, address indexed sender, address[] receivers, uint256[] tokenIds, uint256[] amounts) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | +| amounts | uint256[] | undefined | + +### MintableTokenMapped + +```solidity +event MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + diff --git a/docs/root/ChildMintableERC20Predicate.md b/docs/root/ChildMintableERC20Predicate.md new file mode 100644 index 00000000..536c5f2d --- /dev/null +++ b/docs/root/ChildMintableERC20Predicate.md @@ -0,0 +1,304 @@ +# ChildMintableERC20Predicate + +*Polygon Technology (@QEDK)* + +> ChildMintableERC20Predicate + +Enables ERC20 token deposits and withdrawals across an arbitrary root chain and child chain + + + +## Methods + +### DEPOSIT_SIG + +```solidity +function DEPOSIT_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### MAP_TOKEN_SIG + +```solidity +function MAP_TOKEN_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### WITHDRAW_SIG + +```solidity +function WITHDRAW_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### childTokenTemplate + +```solidity +function childTokenTemplate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### exitHelper + +```solidity +function exitHelper() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### initialize + +```solidity +function initialize(address newStateSender, address newExitHelper, address newRootERC20Predicate, address newChildTokenTemplate) external nonpayable +``` + +Initilization function for RootERC20Predicate + +*Can only be called once.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| newStateSender | address | Address of StateSender to send deposit information to | +| newExitHelper | address | Address of ExitHelper to receive withdrawal information from | +| newRootERC20Predicate | address | Address of child ERC20 predicate to communicate with | +| newChildTokenTemplate | address | undefined | + +### onL2StateReceive + +```solidity +function onL2StateReceive(uint256, address sender, bytes data) external nonpayable +``` + +Function to be used for token deposits + +*Can be extended to include other signatures for more functionality* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | +| sender | address | Address of the sender on the root chain | +| data | bytes | Data sent by the sender | + +### rootERC20Predicate + +```solidity +function rootERC20Predicate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### rootTokenToChildToken + +```solidity +function rootTokenToChildToken(address) external view returns (address) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### stateSender + +```solidity +function stateSender() external view returns (contract IStateSender) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | contract IStateSender | undefined | + +### withdraw + +```solidity +function withdraw(contract IChildERC20 childToken, uint256 amount) external nonpayable +``` + +Function to withdraw tokens from the withdrawer to themselves on the root chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC20 | Address of the child token being withdrawn | +| amount | uint256 | Amount to withdraw | + +### withdrawTo + +```solidity +function withdrawTo(contract IChildERC20 childToken, address receiver, uint256 amount) external nonpayable +``` + +Function to withdraw tokens from the withdrawer to another address on the root chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC20 | Address of the child token being withdrawn | +| receiver | address | Address of the receiver on the root chain | +| amount | uint256 | Amount to withdraw | + + + +## Events + +### Initialized + +```solidity +event Initialized(uint8 version) +``` + + + +*Triggered when the contract has been initialized or reinitialized.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| version | uint8 | undefined | + +### MintableERC20Deposit + +```solidity +event MintableERC20Deposit(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| amount | uint256 | undefined | + +### MintableERC20Withdraw + +```solidity +event MintableERC20Withdraw(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 amount) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| amount | uint256 | undefined | + +### MintableTokenMapped + +```solidity +event MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + diff --git a/docs/root/ChildMintableERC721Predicate.md b/docs/root/ChildMintableERC721Predicate.md new file mode 100644 index 00000000..b60b305b --- /dev/null +++ b/docs/root/ChildMintableERC721Predicate.md @@ -0,0 +1,396 @@ +# ChildMintableERC721Predicate + +*Polygon Technology (@QEDK)* + +> ChildMintableERC721Predicate + +Enables mintable ERC721 token deposits and withdrawals across an arbitrary root chain and child chain + + + +## Methods + +### DEPOSIT_BATCH_SIG + +```solidity +function DEPOSIT_BATCH_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### DEPOSIT_SIG + +```solidity +function DEPOSIT_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### MAP_TOKEN_SIG + +```solidity +function MAP_TOKEN_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### WITHDRAW_BATCH_SIG + +```solidity +function WITHDRAW_BATCH_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### WITHDRAW_SIG + +```solidity +function WITHDRAW_SIG() external view returns (bytes32) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | bytes32 | undefined | + +### childTokenTemplate + +```solidity +function childTokenTemplate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### exitHelper + +```solidity +function exitHelper() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### initialize + +```solidity +function initialize(address newStateSender, address newExitHelper, address newRootERC721Predicate, address newChildTokenTemplate) external nonpayable +``` + +Initilization function for ChildERC721Predicate + +*Can only be called once. `newNativeTokenRootAddress` should be set to zero where root token does not exist.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| newStateSender | address | Address of StateSender to send exit information to | +| newExitHelper | address | Address of StateReceiver to receive deposit information from | +| newRootERC721Predicate | address | Address of root ERC721 predicate to communicate with | +| newChildTokenTemplate | address | Address of child token implementation to deploy clones of | + +### onL2StateReceive + +```solidity +function onL2StateReceive(uint256, address sender, bytes data) external nonpayable +``` + +Function to be used for token deposits + +*Can be extended to include other signatures for more functionality* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | uint256 | undefined | +| sender | address | Address of the sender on the root chain | +| data | bytes | Data sent by the sender | + +### rootERC721Predicate + +```solidity +function rootERC721Predicate() external view returns (address) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### rootTokenToChildToken + +```solidity +function rootTokenToChildToken(address) external view returns (address) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | address | undefined | + +### stateSender + +```solidity +function stateSender() external view returns (contract IStateSender) +``` + + + + + + +#### Returns + +| Name | Type | Description | +|---|---|---| +| _0 | contract IStateSender | undefined | + +### withdraw + +```solidity +function withdraw(contract IChildERC721 childToken, uint256 tokenId) external nonpayable +``` + +Function to withdraw tokens from the withdrawer to themselves on the root chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC721 | Address of the child token being withdrawn | +| tokenId | uint256 | index of the NFT to withdraw | + +### withdrawBatch + +```solidity +function withdrawBatch(contract IChildERC721 childToken, address[] receivers, uint256[] tokenIds) external nonpayable +``` + +Function to batch withdraw tokens from the withdrawer to other addresses on the root chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC721 | Address of the child token being withdrawn | +| receivers | address[] | Addresses of the receivers on the root chain | +| tokenIds | uint256[] | indices of the NFTs to withdraw | + +### withdrawTo + +```solidity +function withdrawTo(contract IChildERC721 childToken, address receiver, uint256 tokenId) external nonpayable +``` + +Function to withdraw tokens from the withdrawer to another address on the root chain + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| childToken | contract IChildERC721 | Address of the child token being withdrawn | +| receiver | address | Address of the receiver on the root chain | +| tokenId | uint256 | index of the NFT to withdraw | + + + +## Events + +### Initialized + +```solidity +event Initialized(uint8 version) +``` + + + +*Triggered when the contract has been initialized or reinitialized.* + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| version | uint8 | undefined | + +### MintableERC721Deposit + +```solidity +event MintableERC721Deposit(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 tokenId) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | + +### MintableERC721DepositBatch + +```solidity +event MintableERC721DepositBatch(address indexed rootToken, address indexed childToken, address indexed sender, address[] receivers, uint256[] tokenIds) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | + +### MintableERC721Withdraw + +```solidity +event MintableERC721Withdraw(address indexed rootToken, address indexed childToken, address sender, address indexed receiver, uint256 tokenId) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender | address | undefined | +| receiver `indexed` | address | undefined | +| tokenId | uint256 | undefined | + +### MintableERC721WithdrawBatch + +```solidity +event MintableERC721WithdrawBatch(address indexed rootToken, address indexed childToken, address indexed sender, address[] receivers, uint256[] tokenIds) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | +| sender `indexed` | address | undefined | +| receivers | address[] | undefined | +| tokenIds | uint256[] | undefined | + +### MintableTokenMapped + +```solidity +event MintableTokenMapped(address indexed rootToken, address indexed childToken) +``` + + + + + +#### Parameters + +| Name | Type | Description | +|---|---|---| +| rootToken `indexed` | address | undefined | +| childToken `indexed` | address | undefined | + + + diff --git a/package-lock.json b/package-lock.json index 8d105316..038aafb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,10 +20,10 @@ "devDependencies": { "@defi-wonderland/smock": "^2.3.4", "@nomicfoundation/hardhat-toolbox": "^2.0.2", - "@openzeppelin/hardhat-upgrades": "^1.25.0", + "@openzeppelin/hardhat-upgrades": "^1.26.0", "@types/chai": "^4.3.5", "@types/mocha": "^10.0.1", - "@types/node": "^20.1.1", + "@types/node": "^20.1.3", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "dotenv": "^16.0.3", @@ -1519,16 +1519,16 @@ "integrity": "sha512-SXDRl7HKpl2WDoJpn7CK/M9U4Z8gNXDHHChAKh0Iz+Wew3wu6CmFYBeie3je8V0GSXZAIYYwUktSrnW/kwVPtg==" }, "node_modules/@openzeppelin/hardhat-upgrades": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.25.0.tgz", - "integrity": "sha512-8kFCHMPLVdbegE4pItBmtEphK1dZE5rXIegOs2lgnOoGOuI+0hSpFNFlhFD8nYYP5YAsuFnxuFbCHVribWF4Rg==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.26.0.tgz", + "integrity": "sha512-ggCvdIf7A9QcMedCaswecgt3N7hacx3qYOn+4bNPqMAwslo/SJ9vN4AhV0VWkDcY4CqFFou3RFQmDWNeLMBX9A==", "dev": true, "dependencies": { - "@openzeppelin/upgrades-core": "^1.26.0", + "@openzeppelin/upgrades-core": "^1.26.2", "chalk": "^4.1.0", "debug": "^4.1.1", - "defender-admin-client": "^1.39.0", - "platform-deploy-client": "^0.3.2", + "defender-base-client": "^1.44.0", + "platform-deploy-client": "^0.6.0", "proper-lockfile": "^4.1.1" }, "bin": { @@ -1617,9 +1617,9 @@ } }, "node_modules/@openzeppelin/upgrades-core": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.26.0.tgz", - "integrity": "sha512-iUrHdTvMxV4zWQ/baBTz3PMmnhoDyJ+TsnlH4/k15eutQi8G6YnS7ca8GAKm/9RFwHPeSjVcqPuWwa1QqL/1hw==", + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.26.2.tgz", + "integrity": "sha512-TJORrgyun5qflPos/47P3j61gDw+7W+tEirSBOYRxfVL1WGjX1n8iaLrijPIqzyeS1MKguN1nckAMspQ4SKrxw==", "dev": true, "dependencies": { "cbor": "^8.0.0", @@ -2052,9 +2052,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.1.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.1.tgz", - "integrity": "sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A==" + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.3.tgz", + "integrity": "sha512-NP2yfZpgmf2eDRPmgGq+fjGjSwFgYbihA8/gK+ey23qT9RkxsgNTZvGOEpXgzIGqesTYkElELLgtKoMQTys5vA==" }, "node_modules/@types/pbkdf2": { "version": "3.1.0", @@ -3576,23 +3576,10 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/defender-admin-client": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/defender-admin-client/-/defender-admin-client-1.43.0.tgz", - "integrity": "sha512-EAB4dUkpcKjbLSHSTTw9KVmTrs8A36x+eVNt85Qr47ypilILtyNgjdqgvroda8syRicQBy28VBdUUWPJtVYAPA==", - "dev": true, - "dependencies": { - "axios": "^0.21.2", - "defender-base-client": "1.43.0", - "ethers": "^5.7.2", - "lodash": "^4.17.19", - "node-fetch": "^2.6.0" - } - }, "node_modules/defender-base-client": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/defender-base-client/-/defender-base-client-1.43.0.tgz", - "integrity": "sha512-PFQPDZ08SznSlsKiHcvf1TzvKtnd/fv2/P5s2Y8jYPIb7OtANHw94oDFOOvRpg54o8EQItIb9v7H4g4kp/7fng==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/defender-base-client/-/defender-base-client-1.44.0.tgz", + "integrity": "sha512-8ZgGA93+FlxNwG9LN1nu/Au5AyCKwAWJGNf0VLiPmh4GX/Nali/7kv72K+OtZgGxTLtKDKfgN4cnhEZwfrc8dg==", "dev": true, "dependencies": { "amazon-cognito-identity-js": "^6.0.1", @@ -8284,14 +8271,14 @@ } }, "node_modules/platform-deploy-client": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/platform-deploy-client/-/platform-deploy-client-0.3.3.tgz", - "integrity": "sha512-CO7P0h8OHSa19QjA7hWbpmLPE0TPacDgdwA8Vwu9NnUdlYvXfF+q4f0FwaeS0vb1ruWtpkTfeIxy7Sc+TxgcNQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/platform-deploy-client/-/platform-deploy-client-0.6.0.tgz", + "integrity": "sha512-mBfnOvF2gb9acGJjlXBQ6VOAkKFRdljsNKHUVY5xKqzKP2PNh/RqCIvi5AR5NqLMrQ3XaMIwRvmwAjtGw7JhYg==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.6.3", "axios": "^0.21.2", - "defender-base-client": "^1.40.0", + "defender-base-client": "^1.44.0", "lodash": "^4.17.19", "node-fetch": "^2.6.0" } @@ -12325,16 +12312,16 @@ "integrity": "sha512-SXDRl7HKpl2WDoJpn7CK/M9U4Z8gNXDHHChAKh0Iz+Wew3wu6CmFYBeie3je8V0GSXZAIYYwUktSrnW/kwVPtg==" }, "@openzeppelin/hardhat-upgrades": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.25.0.tgz", - "integrity": "sha512-8kFCHMPLVdbegE4pItBmtEphK1dZE5rXIegOs2lgnOoGOuI+0hSpFNFlhFD8nYYP5YAsuFnxuFbCHVribWF4Rg==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.26.0.tgz", + "integrity": "sha512-ggCvdIf7A9QcMedCaswecgt3N7hacx3qYOn+4bNPqMAwslo/SJ9vN4AhV0VWkDcY4CqFFou3RFQmDWNeLMBX9A==", "dev": true, "requires": { - "@openzeppelin/upgrades-core": "^1.26.0", + "@openzeppelin/upgrades-core": "^1.26.2", "chalk": "^4.1.0", "debug": "^4.1.1", - "defender-admin-client": "^1.39.0", - "platform-deploy-client": "^0.3.2", + "defender-base-client": "^1.44.0", + "platform-deploy-client": "^0.6.0", "proper-lockfile": "^4.1.1" }, "dependencies": { @@ -12390,9 +12377,9 @@ } }, "@openzeppelin/upgrades-core": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.26.0.tgz", - "integrity": "sha512-iUrHdTvMxV4zWQ/baBTz3PMmnhoDyJ+TsnlH4/k15eutQi8G6YnS7ca8GAKm/9RFwHPeSjVcqPuWwa1QqL/1hw==", + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.26.2.tgz", + "integrity": "sha512-TJORrgyun5qflPos/47P3j61gDw+7W+tEirSBOYRxfVL1WGjX1n8iaLrijPIqzyeS1MKguN1nckAMspQ4SKrxw==", "dev": true, "requires": { "cbor": "^8.0.0", @@ -12741,9 +12728,9 @@ "dev": true }, "@types/node": { - "version": "20.1.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.1.tgz", - "integrity": "sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A==" + "version": "20.1.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.3.tgz", + "integrity": "sha512-NP2yfZpgmf2eDRPmgGq+fjGjSwFgYbihA8/gK+ey23qT9RkxsgNTZvGOEpXgzIGqesTYkElELLgtKoMQTys5vA==" }, "@types/pbkdf2": { "version": "3.1.0", @@ -13911,23 +13898,10 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "defender-admin-client": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/defender-admin-client/-/defender-admin-client-1.43.0.tgz", - "integrity": "sha512-EAB4dUkpcKjbLSHSTTw9KVmTrs8A36x+eVNt85Qr47ypilILtyNgjdqgvroda8syRicQBy28VBdUUWPJtVYAPA==", - "dev": true, - "requires": { - "axios": "^0.21.2", - "defender-base-client": "1.43.0", - "ethers": "^5.7.2", - "lodash": "^4.17.19", - "node-fetch": "^2.6.0" - } - }, "defender-base-client": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/defender-base-client/-/defender-base-client-1.43.0.tgz", - "integrity": "sha512-PFQPDZ08SznSlsKiHcvf1TzvKtnd/fv2/P5s2Y8jYPIb7OtANHw94oDFOOvRpg54o8EQItIb9v7H4g4kp/7fng==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/defender-base-client/-/defender-base-client-1.44.0.tgz", + "integrity": "sha512-8ZgGA93+FlxNwG9LN1nu/Au5AyCKwAWJGNf0VLiPmh4GX/Nali/7kv72K+OtZgGxTLtKDKfgN4cnhEZwfrc8dg==", "dev": true, "requires": { "amazon-cognito-identity-js": "^6.0.1", @@ -17558,14 +17532,14 @@ "peer": true }, "platform-deploy-client": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/platform-deploy-client/-/platform-deploy-client-0.3.3.tgz", - "integrity": "sha512-CO7P0h8OHSa19QjA7hWbpmLPE0TPacDgdwA8Vwu9NnUdlYvXfF+q4f0FwaeS0vb1ruWtpkTfeIxy7Sc+TxgcNQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/platform-deploy-client/-/platform-deploy-client-0.6.0.tgz", + "integrity": "sha512-mBfnOvF2gb9acGJjlXBQ6VOAkKFRdljsNKHUVY5xKqzKP2PNh/RqCIvi5AR5NqLMrQ3XaMIwRvmwAjtGw7JhYg==", "dev": true, "requires": { "@ethersproject/abi": "^5.6.3", "axios": "^0.21.2", - "defender-base-client": "^1.40.0", + "defender-base-client": "^1.44.0", "lodash": "^4.17.19", "node-fetch": "^2.6.0" } diff --git a/package.json b/package.json index e21f5686..6eb4f97b 100644 --- a/package.json +++ b/package.json @@ -47,10 +47,10 @@ "devDependencies": { "@defi-wonderland/smock": "^2.3.4", "@nomicfoundation/hardhat-toolbox": "^2.0.2", - "@openzeppelin/hardhat-upgrades": "^1.25.0", + "@openzeppelin/hardhat-upgrades": "^1.26.0", "@types/chai": "^4.3.5", "@types/mocha": "^10.0.1", - "@types/node": "^20.1.1", + "@types/node": "^20.1.3", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "dotenv": "^16.0.3", diff --git a/test/child/RootMintableERC1155Predicate.test.ts b/test/child/RootMintableERC1155Predicate.test.ts new file mode 100644 index 00000000..769b7221 --- /dev/null +++ b/test/child/RootMintableERC1155Predicate.test.ts @@ -0,0 +1,361 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { + RootMintableERC1155Predicate, + RootMintableERC1155Predicate__factory, + L2StateSender, + L2StateSender__factory, + StateReceiver, + StateReceiver__factory, + ChildERC1155, + ChildERC1155__factory, + MockERC1155, +} from "../../typechain-types"; +import { setBalance, impersonateAccount } from "@nomicfoundation/hardhat-network-helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +describe("RootMintableERC1155Predicate", () => { + let rootMintableERC1155Predicate: RootMintableERC1155Predicate, + systemRootMintableERC1155Predicate: RootMintableERC1155Predicate, + stateReceiverRootMintableERC1155Predicate: RootMintableERC1155Predicate, + l2StateSender: L2StateSender, + stateReceiver: StateReceiver, + childERC1155Predicate: string, + childTokenTemplate: ChildERC1155, + rootToken: MockERC1155, + totalSupply: number = 0, + accounts: SignerWithAddress[], + id: number = 1337; + before(async () => { + accounts = await ethers.getSigners(); + + const L2StateSender: L2StateSender__factory = await ethers.getContractFactory("L2StateSender"); + l2StateSender = await L2StateSender.deploy(); + + await l2StateSender.deployed(); + + const StateReceiver: StateReceiver__factory = await ethers.getContractFactory("StateReceiver"); + stateReceiver = await StateReceiver.deploy(); + + await stateReceiver.deployed(); + + childERC1155Predicate = ethers.Wallet.createRandom().address; + + const ChildERC1155: ChildERC1155__factory = await ethers.getContractFactory("ChildERC1155"); + childTokenTemplate = await ChildERC1155.deploy(); + + await childTokenTemplate.deployed(); + + const RootMintableERC1155Predicate: RootMintableERC1155Predicate__factory = await ethers.getContractFactory( + "RootMintableERC1155Predicate" + ); + rootMintableERC1155Predicate = await RootMintableERC1155Predicate.deploy(); + + await rootMintableERC1155Predicate.deployed(); + + impersonateAccount("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE"); + setBalance("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE", "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + + systemRootMintableERC1155Predicate = rootMintableERC1155Predicate.connect( + await ethers.getSigner("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") + ); + + impersonateAccount(stateReceiver.address); + setBalance(stateReceiver.address, "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + stateReceiverRootMintableERC1155Predicate = rootMintableERC1155Predicate.connect( + await ethers.getSigner(stateReceiver.address) + ); + }); + + it("fail bad initialization", async () => { + await expect( + systemRootMintableERC1155Predicate.initialize( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ) + ).to.be.revertedWith("RootMintableERC1155Predicate: BAD_INITIALIZATION"); + }); + + it("initialize and validate initialization", async () => { + await systemRootMintableERC1155Predicate.initialize( + l2StateSender.address, + stateReceiver.address, + childERC1155Predicate, + childTokenTemplate.address + ); + + expect(await rootMintableERC1155Predicate.l2StateSender()).to.equal(l2StateSender.address); + expect(await rootMintableERC1155Predicate.stateReceiver()).to.equal(stateReceiver.address); + expect(await rootMintableERC1155Predicate.childERC1155Predicate()).to.equal(childERC1155Predicate); + expect(await rootMintableERC1155Predicate.childTokenTemplate()).to.equal(childTokenTemplate.address); + }); + + it("fail reinitialization", async () => { + await expect( + systemRootMintableERC1155Predicate.initialize( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ) + ).to.be.revertedWith("Initializable: contract is already initialized"); + }); + + it("withdraw tokens fail: only state receiver", async () => { + await expect( + rootMintableERC1155Predicate.onStateReceive(0, "0x0000000000000000000000000000000000000000", "0x00") + ).to.be.revertedWith("RootMintableERC1155Predicate: ONLY_STATE_RECEIVER"); + }); + + it("withdraw tokens fail: only child predicate", async () => { + await expect( + stateReceiverRootMintableERC1155Predicate.onStateReceive(0, ethers.Wallet.createRandom().address, "0x00") + ).to.be.revertedWith("RootMintableERC1155Predicate: ONLY_CHILD_PREDICATE"); + }); + + it("withdraw tokens fail: invalid signature", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.randomBytes(32), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + stateReceiverRootMintableERC1155Predicate.onStateReceive(0, childERC1155Predicate, exitData) + ).to.be.revertedWith("RootMintableERC1155Predicate: INVALID_SIGNATURE"); + }); + + it("withdraw tokens fail: unmapped token", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + ethers.Wallet.createRandom().address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect(stateReceiverRootMintableERC1155Predicate.onStateReceive(0, childERC1155Predicate, exitData)).to.be + .reverted; + }); + + it("map token success", async () => { + rootToken = await (await ethers.getContractFactory("MockERC1155")).deploy(); + const clonesContract = await (await ethers.getContractFactory("MockClones")).deploy(); + const childTokenAddr = await clonesContract.predictDeterministicAddress( + childTokenTemplate.address, + ethers.utils.solidityKeccak256(["address"], [rootToken.address]), + childERC1155Predicate + ); + const mapTx = await rootMintableERC1155Predicate.mapToken(rootToken.address); + const mapReceipt = await mapTx.wait(); + const mapEvent = mapReceipt?.events?.find((log: any) => log.event === "L2MintableTokenMapped"); + expect(mapEvent?.args?.rootToken).to.equal(rootToken.address); + expect(mapEvent?.args?.childToken).to.equal(childTokenAddr); + expect(await rootMintableERC1155Predicate.rootTokenToChildToken(rootToken.address)).to.equal(childTokenAddr); + }); + + it("remap token fail", async () => { + await expect(rootMintableERC1155Predicate.mapToken(rootToken.address)).to.be.revertedWith( + "RootMintableERC1155Predicate: ALREADY_MAPPED" + ); + }); + + it("map token fail: zero address", async () => { + await expect( + rootMintableERC1155Predicate.mapToken("0x0000000000000000000000000000000000000000") + ).to.be.revertedWith("RootMintableERC1155Predicate: INVALID_TOKEN"); + }); + + it("withdraw tokens fail: predicate does not have supply", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + rootToken.address, + accounts[0].address, + accounts[0].address, + 1, + ] + ); + await expect(stateReceiverRootMintableERC1155Predicate.onStateReceive(0, childERC1155Predicate, exitData)).to.be + .reverted; + }); + + it("deposit unmapped token: success", async () => { + const randomAmount = Math.floor(Math.random() * 1000000 + 1); + const tempRootToken = await (await ethers.getContractFactory("MockERC1155")).deploy(); + await tempRootToken.mint(accounts[0].address, id, randomAmount, []); + await tempRootToken.setApprovalForAll(rootMintableERC1155Predicate.address, true); + const depositTx = await rootMintableERC1155Predicate.deposit(tempRootToken.address, id, randomAmount); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC1155Deposit"); + const childToken = await rootMintableERC1155Predicate.rootTokenToChildToken(tempRootToken.address); + await expect(depositTx) + .to.emit(rootMintableERC1155Predicate, "L2MintableTokenMapped") + .withArgs(tempRootToken.address, childToken); + expect(depositEvent?.args?.rootToken).to.equal(tempRootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.tokenId).to.equal(id); + expect(depositEvent?.args?.amount).to.equal(randomAmount); + }); + + it("deposit tokens to same address: success", async () => { + const randomAmount = Math.floor(Math.random() * 1000000 + 1); + totalSupply += randomAmount; + await rootToken.mint(accounts[0].address, id, randomAmount, []); + await rootToken.setApprovalForAll(rootMintableERC1155Predicate.address, true); + const depositTx = await rootMintableERC1155Predicate.deposit(rootToken.address, id, randomAmount); + await expect(depositTx).to.not.emit(rootMintableERC1155Predicate, "L2MintableTokenMapped"); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC1155Deposit"); + const childToken = await rootMintableERC1155Predicate.rootTokenToChildToken(rootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(rootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.tokenId).to.equal(id); + expect(depositEvent?.args?.amount).to.equal(randomAmount); + }); + + it("deposit tokens to different address: success", async () => { + const randomAmount = Math.floor(Math.random() * 1000000 + 1); + totalSupply += randomAmount; + await rootToken.mint(accounts[0].address, id, randomAmount, []); + const depositTx = await rootMintableERC1155Predicate.depositTo( + rootToken.address, + accounts[1].address, + id, + randomAmount + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC1155Deposit"); + const childToken = await rootMintableERC1155Predicate.rootTokenToChildToken(rootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(rootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[1].address); + expect(depositEvent?.args?.tokenId).to.equal(id); + expect(depositEvent?.args?.amount).to.equal(randomAmount); + }); + + it("batch deposit tokens to same address: success", async () => { + const receivers = [accounts[0].address, accounts[1].address, accounts[2].address]; + const ids = [id + 1, id + 2, id + 3]; + const amounts = [1, 2, 3]; + for (let i = 0; i < ids.length; i++) { + await rootToken.mint(accounts[0].address, ids[i], amounts[i], []); + } + await rootToken.setApprovalForAll(rootMintableERC1155Predicate.address, true); + const depositTx = await rootMintableERC1155Predicate.depositBatch(rootToken.address, receivers, ids, amounts); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC1155DepositBatch"); + const childToken = await rootMintableERC1155Predicate.rootTokenToChildToken(rootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(rootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receivers).to.deep.equal(receivers); + expect(depositEvent?.args?.tokenIds).to.deep.equal(ids); + expect(depositEvent?.args?.amounts).to.deep.equal(amounts); + }); + + it("withdraw tokens to same address: success", async () => { + const randomAmount = Math.floor(Math.random() * totalSupply + 1); + totalSupply -= randomAmount; + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + rootToken.address, + accounts[0].address, + accounts[0].address, + id, + randomAmount, + ] + ); + const withdrawTx = await stateReceiverRootMintableERC1155Predicate.onStateReceive( + 0, + childERC1155Predicate, + exitData + ); + const withdrawReceipt = await withdrawTx.wait(); + const withdrawEvent = withdrawReceipt?.events?.find((log: any) => log.event === "L2MintableERC1155Withdraw"); + const childToken = await rootMintableERC1155Predicate.rootTokenToChildToken(rootToken.address); + expect(withdrawEvent?.args?.rootToken).to.equal(rootToken.address); + expect(withdrawEvent?.args?.childToken).to.equal(childToken); + expect(withdrawEvent?.args?.withdrawer).to.equal(accounts[0].address); + expect(withdrawEvent?.args?.receiver).to.equal(accounts[0].address); + expect(withdrawEvent?.args?.tokenId).to.equal(id); + expect(withdrawEvent?.args?.amount).to.equal(randomAmount); + }); + + it("withdraw tokens to different address: success", async () => { + const randomAmount = Math.floor(Math.random() * totalSupply + 1); + totalSupply -= randomAmount; + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + rootToken.address, + accounts[0].address, + accounts[1].address, + id, + randomAmount, + ] + ); + const withdrawTx = await stateReceiverRootMintableERC1155Predicate.onStateReceive( + 0, + childERC1155Predicate, + exitData + ); + const withdrawReceipt = await withdrawTx.wait(); + const withdrawEvent = withdrawReceipt?.events?.find((log: any) => log.event === "L2MintableERC1155Withdraw"); + const childToken = await rootMintableERC1155Predicate.rootTokenToChildToken(rootToken.address); + expect(withdrawEvent?.args?.rootToken).to.equal(rootToken.address); + expect(withdrawEvent?.args?.childToken).to.equal(childToken); + expect(withdrawEvent?.args?.withdrawer).to.equal(accounts[0].address); + expect(withdrawEvent?.args?.receiver).to.equal(accounts[1].address); + expect(withdrawEvent?.args?.tokenId).to.equal(id); + expect(withdrawEvent?.args?.amount).to.equal(randomAmount); + }); + + it("batch withdraw tokens to same address: success", async () => { + const receivers = [accounts[0].address, accounts[1].address, accounts[2].address]; + const ids = [id + 1, id + 2, id + 3]; + const amounts = [1, 2, 3]; + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address[]", "uint256[]", "uint256[]"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW_BATCH"]), + rootToken.address, + accounts[0].address, + receivers, + ids, + amounts, + ] + ); + const withdrawTx = await stateReceiverRootMintableERC1155Predicate.onStateReceive( + 0, + childERC1155Predicate, + exitData + ); + const withdrawReceipt = await withdrawTx.wait(); + const withdrawEvent = withdrawReceipt?.events?.find((log: any) => log.event === "L2MintableERC1155WithdrawBatch"); + const childToken = await rootMintableERC1155Predicate.rootTokenToChildToken(rootToken.address); + expect(withdrawEvent?.args?.rootToken).to.equal(rootToken.address); + expect(withdrawEvent?.args?.childToken).to.equal(childToken); + expect(withdrawEvent?.args?.withdrawer).to.equal(accounts[0].address); + expect(withdrawEvent?.args?.receivers).to.deep.equal(receivers); + expect(withdrawEvent?.args?.tokenIds).to.deep.equal(ids); + expect(withdrawEvent?.args?.amounts).to.deep.equal(amounts); + }); +}); diff --git a/test/child/RootMintableERC20Predicate.test.ts b/test/child/RootMintableERC20Predicate.test.ts new file mode 100644 index 00000000..883ef3b1 --- /dev/null +++ b/test/child/RootMintableERC20Predicate.test.ts @@ -0,0 +1,316 @@ +import { expect } from "chai"; +import { ethers, network } from "hardhat"; +import { + RootMintableERC20Predicate, + RootMintableERC20Predicate__factory, + L2StateSender, + L2StateSender__factory, + StateReceiver, + StateReceiver__factory, + ChildERC20, + ChildERC20__factory, + NativeERC20, + NativeERC20__factory, + MockERC20, +} from "../../typechain-types"; +import { setCode, setBalance, impersonateAccount } from "@nomicfoundation/hardhat-network-helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { alwaysTrueBytecode } from "../constants"; + +describe("RootMintableERC20Predicate", () => { + let rootMintableERC20Predicate: RootMintableERC20Predicate, + systemRootMintableERC20Predicate: RootMintableERC20Predicate, + stateReceiverRootMintableERC20Predicate: RootMintableERC20Predicate, + l2StateSender: L2StateSender, + stateReceiver: StateReceiver, + childERC20Predicate: string, + childTokenTemplate: ChildERC20, + rootToken: MockERC20, + totalSupply: number = 0, + accounts: SignerWithAddress[]; + before(async () => { + accounts = await ethers.getSigners(); + + const L2StateSender: L2StateSender__factory = await ethers.getContractFactory("L2StateSender"); + l2StateSender = await L2StateSender.deploy(); + + await l2StateSender.deployed(); + + const StateReceiver: StateReceiver__factory = await ethers.getContractFactory("StateReceiver"); + stateReceiver = await StateReceiver.deploy(); + + await stateReceiver.deployed(); + + childERC20Predicate = ethers.Wallet.createRandom().address; + + const ChildERC20: ChildERC20__factory = await ethers.getContractFactory("ChildERC20"); + childTokenTemplate = await ChildERC20.deploy(); + + await childTokenTemplate.deployed(); + + const RootMintableERC20Predicate: RootMintableERC20Predicate__factory = await ethers.getContractFactory( + "RootMintableERC20Predicate" + ); + rootMintableERC20Predicate = await RootMintableERC20Predicate.deploy(); + + await rootMintableERC20Predicate.deployed(); + + const NativeERC20: NativeERC20__factory = await ethers.getContractFactory("NativeERC20"); + + const tempNativeERC20 = await NativeERC20.deploy(); + + await tempNativeERC20.deployed(); + + await setCode( + "0x0000000000000000000000000000000000001010", + await network.provider.send("eth_getCode", [tempNativeERC20.address]) + ); // Mock genesis NativeERC20 deployment + + NativeERC20.attach("0x0000000000000000000000000000000000001010") as NativeERC20; + + await setCode("0x0000000000000000000000000000000000002020", alwaysTrueBytecode); // Mock NATIVE_TRANSFER_PRECOMPILE + + impersonateAccount("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE"); + setBalance("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE", "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + + systemRootMintableERC20Predicate = rootMintableERC20Predicate.connect( + await ethers.getSigner("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") + ); + + impersonateAccount(stateReceiver.address); + setBalance(stateReceiver.address, "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + stateReceiverRootMintableERC20Predicate = rootMintableERC20Predicate.connect( + await ethers.getSigner(stateReceiver.address) + ); + }); + + it("fail bad initialization", async () => { + await expect( + systemRootMintableERC20Predicate.initialize( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ) + ).to.be.revertedWith("RootMintableERC20Predicate: BAD_INITIALIZATION"); + }); + + it("initialize and validate initialization", async () => { + await systemRootMintableERC20Predicate.initialize( + l2StateSender.address, + stateReceiver.address, + childERC20Predicate, + childTokenTemplate.address + ); + + expect(await rootMintableERC20Predicate.l2StateSender()).to.equal(l2StateSender.address); + expect(await rootMintableERC20Predicate.stateReceiver()).to.equal(stateReceiver.address); + expect(await rootMintableERC20Predicate.childERC20Predicate()).to.equal(childERC20Predicate); + expect(await rootMintableERC20Predicate.childTokenTemplate()).to.equal(childTokenTemplate.address); + }); + + it("fail reinitialization", async () => { + await expect( + systemRootMintableERC20Predicate.initialize( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ) + ).to.be.revertedWith("Initializable: contract is already initialized"); + }); + + it("withdraw tokens fail: only exit helper", async () => { + await expect( + rootMintableERC20Predicate.onStateReceive(0, "0x0000000000000000000000000000000000000000", "0x00") + ).to.be.revertedWith("RootMintableERC20Predicate: ONLY_STATE_RECEIVER"); + }); + + it("withdraw tokens fail: only child predicate", async () => { + await expect( + stateReceiverRootMintableERC20Predicate.onStateReceive(0, ethers.Wallet.createRandom().address, "0x00") + ).to.be.revertedWith("RootMintableERC20Predicate: ONLY_CHILD_PREDICATE"); + }); + + it("withdraw tokens fail: invalid signature", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.randomBytes(32), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + stateReceiverRootMintableERC20Predicate.onStateReceive(0, childERC20Predicate, exitData) + ).to.be.revertedWith("RootMintableERC20Predicate: INVALID_SIGNATURE"); + }); + + it("withdraw tokens fail: unmapped token", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + ethers.Wallet.createRandom().address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + stateReceiverRootMintableERC20Predicate.onStateReceive(0, childERC20Predicate, exitData) + ).to.be.revertedWithPanic(); + }); + + it("map token success", async () => { + rootToken = await (await ethers.getContractFactory("MockERC20")).deploy(); + const clonesContract = await (await ethers.getContractFactory("MockClones")).deploy(); + const childTokenAddr = await clonesContract.predictDeterministicAddress( + childTokenTemplate.address, + ethers.utils.solidityKeccak256(["address"], [rootToken.address]), + childERC20Predicate + ); + const mapTx = await rootMintableERC20Predicate.mapToken(rootToken.address); + const mapReceipt = await mapTx.wait(); + const mapEvent = mapReceipt?.events?.find((log: any) => log.event === "L2MintableTokenMapped"); + expect(mapEvent?.args?.rootToken).to.equal(rootToken.address); + expect(mapEvent?.args?.childToken).to.equal(childTokenAddr); + expect(await rootMintableERC20Predicate.rootTokenToChildToken(rootToken.address)).to.equal(childTokenAddr); + }); + + it("remap token fail", async () => { + await expect(rootMintableERC20Predicate.mapToken(rootToken.address)).to.be.revertedWith( + "RootMintableERC20Predicate: ALREADY_MAPPED" + ); + }); + + it("map token fail: zero address", async () => { + await expect(rootMintableERC20Predicate.mapToken("0x0000000000000000000000000000000000000000")).to.be.revertedWith( + "RootMintableERC20Predicate: INVALID_TOKEN" + ); + }); + + it("withdraw tokens fail: predicate does not have supply", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + rootToken.address, + accounts[0].address, + accounts[0].address, + 1, + ] + ); + await expect( + stateReceiverRootMintableERC20Predicate.onStateReceive(0, childERC20Predicate, exitData) + ).to.be.revertedWith("ERC20: transfer amount exceeds balance"); + }); + + it("deposit unmapped token: success", async () => { + const randomAmount = Math.floor(Math.random() * 1000000 + 1); + const tempRootToken = await (await ethers.getContractFactory("MockERC20")).deploy(); + await tempRootToken.mint(accounts[0].address, ethers.utils.parseUnits(String(randomAmount))); + await tempRootToken.approve(rootMintableERC20Predicate.address, ethers.utils.parseUnits(String(randomAmount))); + const depositTx = await rootMintableERC20Predicate.deposit( + tempRootToken.address, + ethers.utils.parseUnits(String(randomAmount)) + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC20Deposit"); + const childToken = await rootMintableERC20Predicate.rootTokenToChildToken(tempRootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(tempRootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("deposit tokens to same address: success", async () => { + const randomAmount = Math.floor(Math.random() * 1000000 + 1); + totalSupply += randomAmount; + await rootToken.mint(accounts[0].address, ethers.utils.parseUnits(String(randomAmount))); + await rootToken.approve(rootMintableERC20Predicate.address, ethers.utils.parseUnits(String(randomAmount))); + const depositTx = await rootMintableERC20Predicate.deposit( + rootToken.address, + ethers.utils.parseUnits(String(randomAmount)) + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC20Deposit"); + const childToken = await rootMintableERC20Predicate.rootTokenToChildToken(rootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(rootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("deposit tokens to different address: success", async () => { + const randomAmount = Math.floor(Math.random() * 1000000 + 1); + totalSupply += randomAmount; + await rootToken.mint(accounts[0].address, ethers.utils.parseUnits(String(randomAmount))); + await rootToken.approve(rootMintableERC20Predicate.address, ethers.utils.parseUnits(String(randomAmount))); + const depositTx = await rootMintableERC20Predicate.depositTo( + rootToken.address, + accounts[1].address, + ethers.utils.parseUnits(String(randomAmount)) + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC20Deposit"); + const childToken = await rootMintableERC20Predicate.rootTokenToChildToken(rootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(rootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[1].address); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("withdraw tokens to same address: success", async () => { + const randomAmount = Math.floor(Math.random() * totalSupply + 1); + totalSupply -= randomAmount; + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + rootToken.address, + accounts[0].address, + accounts[0].address, + ethers.utils.parseUnits(String(randomAmount)), + ] + ); + const withdrawTx = await stateReceiverRootMintableERC20Predicate.onStateReceive(0, childERC20Predicate, exitData); + const withdrawReceipt = await withdrawTx.wait(); + const withdrawEvent = withdrawReceipt?.events?.find((log: any) => log.event === "L2MintableERC20Withdraw"); + const childToken = await rootMintableERC20Predicate.rootTokenToChildToken(rootToken.address); + expect(withdrawEvent?.args?.rootToken).to.equal(rootToken.address); + expect(withdrawEvent?.args?.childToken).to.equal(childToken); + expect(withdrawEvent?.args?.withdrawer).to.equal(accounts[0].address); + expect(withdrawEvent?.args?.receiver).to.equal(accounts[0].address); + expect(withdrawEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("withdraw tokens to different address: success", async () => { + const randomAmount = Math.floor(Math.random() * totalSupply + 1); + totalSupply -= randomAmount; + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + rootToken.address, + accounts[0].address, + accounts[1].address, + ethers.utils.parseUnits(String(randomAmount)), + ] + ); + const withdrawTx = await stateReceiverRootMintableERC20Predicate.onStateReceive(0, childERC20Predicate, exitData); + const withdrawReceipt = await withdrawTx.wait(); + const withdrawEvent = withdrawReceipt?.events?.find((log: any) => log.event === "L2MintableERC20Withdraw"); + const childToken = await rootMintableERC20Predicate.rootTokenToChildToken(rootToken.address); + expect(withdrawEvent?.args?.rootToken).to.equal(rootToken.address); + expect(withdrawEvent?.args?.childToken).to.equal(childToken); + expect(withdrawEvent?.args?.withdrawer).to.equal(accounts[0].address); + expect(withdrawEvent?.args?.receiver).to.equal(accounts[1].address); + expect(withdrawEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); +}); diff --git a/test/child/RootMintableERC721Predicate.test.ts b/test/child/RootMintableERC721Predicate.test.ts new file mode 100644 index 00000000..1d83632f --- /dev/null +++ b/test/child/RootMintableERC721Predicate.test.ts @@ -0,0 +1,341 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { + RootMintableERC721Predicate, + RootMintableERC721Predicate__factory, + L2StateSender, + L2StateSender__factory, + StateReceiver, + StateReceiver__factory, + ChildERC721, + ChildERC721__factory, + MockERC721, +} from "../../typechain-types"; +import { setBalance, impersonateAccount } from "@nomicfoundation/hardhat-network-helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +describe("RootMintableERC721Predicate", () => { + let rootMintableERC721Predicate: RootMintableERC721Predicate, + systemRootMintableERC721Predicate: RootMintableERC721Predicate, + stateReceiverRootMintableERC721Predicate: RootMintableERC721Predicate, + l2StateSender: L2StateSender, + stateReceiver: StateReceiver, + childERC721Predicate: string, + childTokenTemplate: ChildERC721, + rootToken: MockERC721, + depositedBatchIds: number[] = [], + accounts: SignerWithAddress[]; + before(async () => { + accounts = await ethers.getSigners(); + + const L2StateSender: L2StateSender__factory = await ethers.getContractFactory("L2StateSender"); + l2StateSender = await L2StateSender.deploy(); + + await l2StateSender.deployed(); + + const StateReceiver: StateReceiver__factory = await ethers.getContractFactory("StateReceiver"); + stateReceiver = await StateReceiver.deploy(); + + await stateReceiver.deployed(); + + childERC721Predicate = ethers.Wallet.createRandom().address; + + const ChildERC721: ChildERC721__factory = await ethers.getContractFactory("ChildERC721"); + childTokenTemplate = await ChildERC721.deploy(); + + await childTokenTemplate.deployed(); + + const RootMintableERC721Predicate: RootMintableERC721Predicate__factory = await ethers.getContractFactory( + "RootMintableERC721Predicate" + ); + rootMintableERC721Predicate = await RootMintableERC721Predicate.deploy(); + + await rootMintableERC721Predicate.deployed(); + + impersonateAccount("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE"); + setBalance("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE", "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + + systemRootMintableERC721Predicate = rootMintableERC721Predicate.connect( + await ethers.getSigner("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") + ); + + impersonateAccount(stateReceiver.address); + setBalance(stateReceiver.address, "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + stateReceiverRootMintableERC721Predicate = rootMintableERC721Predicate.connect( + await ethers.getSigner(stateReceiver.address) + ); + }); + + it("fail bad initialization", async () => { + await expect( + systemRootMintableERC721Predicate.initialize( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ) + ).to.be.revertedWith("RootMintableERC721Predicate: BAD_INITIALIZATION"); + }); + + it("initialize and validate initialization", async () => { + await systemRootMintableERC721Predicate.initialize( + l2StateSender.address, + stateReceiver.address, + childERC721Predicate, + childTokenTemplate.address + ); + + expect(await rootMintableERC721Predicate.l2StateSender()).to.equal(l2StateSender.address); + expect(await rootMintableERC721Predicate.stateReceiver()).to.equal(stateReceiver.address); + expect(await rootMintableERC721Predicate.childERC721Predicate()).to.equal(childERC721Predicate); + expect(await rootMintableERC721Predicate.childTokenTemplate()).to.equal(childTokenTemplate.address); + }); + + it("fail reinitialization", async () => { + await expect( + systemRootMintableERC721Predicate.initialize( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ) + ).to.be.revertedWith("Initializable: contract is already initialized"); + }); + + it("withdraw tokens fail: only exit helper", async () => { + await expect( + rootMintableERC721Predicate.onStateReceive(0, "0x0000000000000000000000000000000000000000", "0x00") + ).to.be.revertedWith("RootMintableERC721Predicate: ONLY_STATE_RECEIVER"); + }); + + it("withdraw tokens fail: only child predicate", async () => { + await expect( + stateReceiverRootMintableERC721Predicate.onStateReceive(0, ethers.Wallet.createRandom().address, "0x00") + ).to.be.revertedWith("RootMintableERC721Predicate: ONLY_CHILD_PREDICATE"); + }); + + it("withdraw tokens fail: invalid signature", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.randomBytes(32), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + stateReceiverRootMintableERC721Predicate.onStateReceive(0, childERC721Predicate, exitData) + ).to.be.revertedWith("RootMintableERC721Predicate: INVALID_SIGNATURE"); + }); + + it("withdraw tokens fail: unmapped token", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + ethers.Wallet.createRandom().address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + stateReceiverRootMintableERC721Predicate.onStateReceive(0, childERC721Predicate, exitData) + ).to.be.revertedWithPanic(); + }); + + it("map token success", async () => { + rootToken = await (await ethers.getContractFactory("MockERC721")).deploy(); + const clonesContract = await (await ethers.getContractFactory("MockClones")).deploy(); + const childTokenAddr = await clonesContract.predictDeterministicAddress( + childTokenTemplate.address, + ethers.utils.solidityKeccak256(["address"], [rootToken.address]), + childERC721Predicate + ); + const mapTx = await rootMintableERC721Predicate.mapToken(rootToken.address); + const mapReceipt = await mapTx.wait(); + const mapEvent = mapReceipt?.events?.find((log: any) => log.event === "L2MintableTokenMapped"); + expect(mapEvent?.args?.rootToken).to.equal(rootToken.address); + expect(mapEvent?.args?.childToken).to.equal(childTokenAddr); + expect(await rootMintableERC721Predicate.rootTokenToChildToken(rootToken.address)).to.equal(childTokenAddr); + }); + + it("remap token fail", async () => { + await expect(rootMintableERC721Predicate.mapToken(rootToken.address)).to.be.revertedWith( + "RootMintableERC721Predicate: ALREADY_MAPPED" + ); + }); + + it("map token fail: zero address", async () => { + await expect(rootMintableERC721Predicate.mapToken("0x0000000000000000000000000000000000000000")).to.be.revertedWith( + "RootMintableERC721Predicate: INVALID_TOKEN" + ); + }); + + it("withdraw tokens fail: predicate does not have supply", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + rootToken.address, + accounts[0].address, + accounts[0].address, + 1, + ] + ); + await expect( + stateReceiverRootMintableERC721Predicate.onStateReceive(0, childERC721Predicate, exitData) + ).to.be.revertedWith("ERC721: invalid token ID"); + }); + + it("deposit unmapped token: success", async () => { + const tempRootToken = await (await ethers.getContractFactory("MockERC721")).deploy(); + await tempRootToken.mint(accounts[0].address); + await tempRootToken.approve(rootMintableERC721Predicate.address, 0); + const depositTx = await rootMintableERC721Predicate.deposit(tempRootToken.address, 0); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC721Deposit"); + const childToken = await rootMintableERC721Predicate.rootTokenToChildToken(tempRootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(tempRootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.tokenId).to.equal(0); + }); + + it("deposit tokens to same address: success", async () => { + await rootToken.mint(accounts[0].address); + await rootToken.approve(rootMintableERC721Predicate.address, 0); + const depositTx = await rootMintableERC721Predicate.deposit(rootToken.address, 0); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC721Deposit"); + const childToken = await rootMintableERC721Predicate.rootTokenToChildToken(rootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(rootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.tokenId).to.equal(0); + }); + + it("deposit tokens to different address: success", async () => { + await rootToken.mint(accounts[0].address); + await rootToken.approve(rootMintableERC721Predicate.address, 1); + const depositTx = await rootMintableERC721Predicate.depositTo(rootToken.address, accounts[1].address, 1); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC721Deposit"); + const childToken = await rootMintableERC721Predicate.rootTokenToChildToken(rootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(rootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[1].address); + expect(depositEvent?.args?.tokenId).to.equal(1); + }); + + it("batch deposit tokens: success", async () => { + const batchSize = Math.floor(Math.random() * 10 + 2); + const receiverArr = []; + for (let i = 0; i < batchSize; i++) { + await rootToken.mint(accounts[0].address); + await rootToken.approve(rootMintableERC721Predicate.address, i + 2); + depositedBatchIds.push(i + 2); + receiverArr.push(accounts[1].address); + } + const depositTx = await rootMintableERC721Predicate.depositBatch(rootToken.address, receiverArr, depositedBatchIds); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC721DepositBatch"); + const childToken = await rootMintableERC721Predicate.rootTokenToChildToken(rootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(rootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receivers).to.deep.equal(receiverArr); + expect(depositEvent?.args?.tokenIds).to.deep.equal(depositedBatchIds); + }); + + it("batch deposit unmapped token: success", async () => { + const tempRootToken = await (await ethers.getContractFactory("MockERC721")).deploy(); + await tempRootToken.mint(accounts[0].address); + await tempRootToken.approve(rootMintableERC721Predicate.address, 0); + const depositTx = await rootMintableERC721Predicate.depositBatch(tempRootToken.address, [accounts[0].address], [0]); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt?.events?.find((log: any) => log.event === "L2MintableERC721DepositBatch"); + const childToken = await rootMintableERC721Predicate.rootTokenToChildToken(tempRootToken.address); + expect(depositEvent?.args?.rootToken).to.equal(tempRootToken.address); + expect(depositEvent?.args?.childToken).to.equal(childToken); + expect(depositEvent?.args?.depositor).to.equal(accounts[0].address); + expect(depositEvent?.args?.receivers).to.deep.equal([accounts[0].address]); + expect(depositEvent?.args?.tokenIds).to.deep.equal([0]); + }); + + it("withdraw tokens to same address: success", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + rootToken.address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + const withdrawTx = await stateReceiverRootMintableERC721Predicate.onStateReceive(0, childERC721Predicate, exitData); + const withdrawReceipt = await withdrawTx.wait(); + const withdrawEvent = withdrawReceipt?.events?.find((log: any) => log.event === "L2MintableERC721Withdraw"); + const childToken = await rootMintableERC721Predicate.rootTokenToChildToken(rootToken.address); + expect(withdrawEvent?.args?.rootToken).to.equal(rootToken.address); + expect(withdrawEvent?.args?.childToken).to.equal(childToken); + expect(withdrawEvent?.args?.withdrawer).to.equal(accounts[0].address); + expect(withdrawEvent?.args?.receiver).to.equal(accounts[0].address); + expect(withdrawEvent?.args?.tokenId).to.equal(0); + }); + + it("withdraw tokens to different address: success", async () => { + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW"]), + rootToken.address, + accounts[0].address, + accounts[1].address, + 1, + ] + ); + const withdrawTx = await stateReceiverRootMintableERC721Predicate.onStateReceive(0, childERC721Predicate, exitData); + const withdrawReceipt = await withdrawTx.wait(); + const withdrawEvent = withdrawReceipt?.events?.find((log: any) => log.event === "L2MintableERC721Withdraw"); + const childToken = await rootMintableERC721Predicate.rootTokenToChildToken(rootToken.address); + expect(withdrawEvent?.args?.rootToken).to.equal(rootToken.address); + expect(withdrawEvent?.args?.childToken).to.equal(childToken); + expect(withdrawEvent?.args?.withdrawer).to.equal(accounts[0].address); + expect(withdrawEvent?.args?.receiver).to.equal(accounts[1].address); + expect(withdrawEvent?.args?.tokenId).to.equal(1); + }); + + it("batch withdraw tokens: success", async () => { + const batchSize = Math.floor(Math.random() * depositedBatchIds.length + 2); + const receiverArr = []; + for (let i = 0; i < batchSize; i++) { + receiverArr.push(accounts[2].address); + } + const exitData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address[]", "uint256[]"], + [ + ethers.utils.solidityKeccak256(["string"], ["WITHDRAW_BATCH"]), + rootToken.address, + accounts[1].address, + receiverArr, + depositedBatchIds.slice(0, batchSize), + ] + ); + const withdrawTx = await stateReceiverRootMintableERC721Predicate.onStateReceive(0, childERC721Predicate, exitData); + const withdrawReceipt = await withdrawTx.wait(); + const withdrawEvent = withdrawReceipt?.events?.find((log: any) => log.event === "L2MintableERC721WithdrawBatch"); + const childToken = await rootMintableERC721Predicate.rootTokenToChildToken(rootToken.address); + expect(withdrawEvent?.args?.rootToken).to.equal(rootToken.address); + expect(withdrawEvent?.args?.childToken).to.equal(childToken); + expect(withdrawEvent?.args?.withdrawer).to.equal(accounts[1].address); + expect(withdrawEvent?.args?.receivers).to.deep.equal(receiverArr); + expect(withdrawEvent?.args?.tokenIds).to.deep.equal(depositedBatchIds.slice(0, batchSize)); + }); +}); diff --git a/test/root/ChildMintableERC1155Predicate.test.ts b/test/root/ChildMintableERC1155Predicate.test.ts new file mode 100644 index 00000000..a8f93c3a --- /dev/null +++ b/test/root/ChildMintableERC1155Predicate.test.ts @@ -0,0 +1,495 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { BigNumber } from "ethers"; +import { + ChildMintableERC1155Predicate, + ChildMintableERC1155Predicate__factory, + StateSender, + StateSender__factory, + ExitHelper, + ExitHelper__factory, + ChildERC1155, + ChildERC1155__factory, +} from "../../typechain-types"; +import { setBalance, impersonateAccount, stopImpersonatingAccount } from "@nomicfoundation/hardhat-network-helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { smock } from "@defi-wonderland/smock"; + +describe("ChildMintableERC1155Predicate", () => { + let childMintableERC1155Predicate: ChildMintableERC1155Predicate, + exitHelperChildERC1155Predicate: ChildMintableERC1155Predicate, + stateSender: StateSender, + exitHelper: ExitHelper, + rootERC1155Predicate: string, + childERC1155: ChildERC1155, + rootToken: string, + childTokenAddr: string, + depositedTokenIds: number[] = [], + batchDepositedTokenIds: number[] = [], + batchDepositedTokenAmounts: BigNumber[] = [], + accounts: SignerWithAddress[]; + before(async () => { + accounts = await ethers.getSigners(); + + const StateSender: StateSender__factory = await ethers.getContractFactory("StateSender"); + stateSender = await StateSender.deploy(); + + await stateSender.deployed(); + + const ExitHelper: ExitHelper__factory = await ethers.getContractFactory("ExitHelper"); + exitHelper = await ExitHelper.deploy(); + + await exitHelper.deployed(); + + rootERC1155Predicate = ethers.Wallet.createRandom().address; + + const ChildERC1155: ChildERC1155__factory = await ethers.getContractFactory("ChildERC1155"); + childERC1155 = await ChildERC1155.deploy(); + + await childERC1155.deployed(); + + const ChildMintableERC1155Predicate: ChildMintableERC1155Predicate__factory = await ethers.getContractFactory( + "ChildMintableERC1155Predicate" + ); + childMintableERC1155Predicate = await ChildMintableERC1155Predicate.deploy(); + + await childMintableERC1155Predicate.deployed(); + + impersonateAccount(exitHelper.address); + setBalance(exitHelper.address, "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + exitHelperChildERC1155Predicate = childMintableERC1155Predicate.connect(await ethers.getSigner(exitHelper.address)); + }); + + it("fail bad initialization", async () => { + await expect( + childMintableERC1155Predicate.initialize( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ) + ).to.be.revertedWith("ChildMintableERC1155Predicate: BAD_INITIALIZATION"); + }); + + it("initialize and validate initialization", async () => { + await childMintableERC1155Predicate.initialize( + stateSender.address, + exitHelper.address, + rootERC1155Predicate, + childERC1155.address + ); + expect(await childMintableERC1155Predicate.stateSender()).to.equal(stateSender.address); + expect(await childMintableERC1155Predicate.exitHelper()).to.equal(exitHelper.address); + expect(await childMintableERC1155Predicate.rootERC1155Predicate()).to.equal(rootERC1155Predicate); + expect(await childMintableERC1155Predicate.childTokenTemplate()).to.equal(childERC1155.address); + }); + + it("fail reinitialization", async () => { + await expect( + childMintableERC1155Predicate.initialize( + stateSender.address, + exitHelper.address, + rootERC1155Predicate, + childERC1155.address + ) + ).to.be.revertedWith("Initializable: contract is already initialized"); + }); + + it("map token success", async () => { + rootToken = ethers.Wallet.createRandom().address; + const clonesContract = await (await ethers.getContractFactory("MockClones")).deploy(); + childTokenAddr = await clonesContract.predictDeterministicAddress( + childERC1155.address, + ethers.utils.solidityKeccak256(["address"], [rootToken]), + childMintableERC1155Predicate.address + ); + const childToken = childERC1155.attach(childTokenAddr); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "string", "string", "uint8"], + [ethers.utils.solidityKeccak256(["string"], ["MAP_TOKEN"]), rootToken, "TEST1", "TEST1", 18] + ); + const mapTx = await exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData); + const mapReceipt = await mapTx.wait(); + const mapEvent = mapReceipt?.events?.find((log) => log.event === "MintableTokenMapped"); + expect(mapEvent?.args?.rootToken).to.equal(rootToken); + expect(mapEvent?.args?.childToken).to.equal(childTokenAddr); + expect(await childToken.predicate()).to.equal(childMintableERC1155Predicate.address); + expect(await childToken.rootToken()).to.equal(rootToken); + }); + + it("map token fail: invalid root token", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "string", "string", "uint8"], + [ + ethers.utils.solidityKeccak256(["string"], ["MAP_TOKEN"]), + "0x0000000000000000000000000000000000000000", + "TEST1", + "TEST1", + 18, + ] + ); + await expect( + exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData) + ).to.be.revertedWithPanic(); + }); + + it("map token fail: already mapped", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "string", "string", "uint8"], + [ethers.utils.solidityKeccak256(["string"], ["MAP_TOKEN"]), rootToken, "TEST1", "TEST1", 18] + ); + await expect( + exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData) + ).to.be.revertedWithPanic(); + }); + + it("deposit tokens from root chain with same address", async () => { + const randomAmount = Math.floor(Math.random() * 1000000 + 1); + depositedTokenIds.push(randomAmount); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + rootToken, + accounts[0].address, + accounts[0].address, + randomAmount, + ethers.utils.parseUnits(String(randomAmount)), + ] + ); + const depositTx = await exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log) => log.event === "MintableERC1155Deposit"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.tokenId).to.equal(randomAmount); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("deposit tokens from root chain with different address", async () => { + const randomAmount = Math.floor(Math.random() * 1000000 + 1); + depositedTokenIds.push(randomAmount); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + rootToken, + accounts[0].address, + accounts[1].address, + randomAmount, + ethers.utils.parseUnits(String(randomAmount)), + ] + ); + const depositTx = await exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log) => log.event === "MintableERC1155Deposit"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[1].address); + expect(depositEvent?.args?.tokenId).to.equal(randomAmount); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("batch deposit tokens from root chain: success", async () => { + const batchSize = Math.floor(Math.random() * 10 + 2); + const receiverArr = []; + for (let i = 0; i < batchSize; i++) { + const randomAmount = Math.floor(Math.random() * 1000000 + 1); + batchDepositedTokenIds.push(randomAmount); + receiverArr.push(accounts[2].address); + } + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address[]", "uint256[]", "uint256[]"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT_BATCH"]), + rootToken, + accounts[0].address, + receiverArr, + batchDepositedTokenIds, + batchDepositedTokenIds.map((tokenId) => ethers.utils.parseUnits(String(tokenId))), + ] + ); + const depositTx = await exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log) => log.event === "MintableERC1155DepositBatch"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receivers).to.deep.equal(receiverArr); + expect(depositEvent?.args?.tokenIds).to.deep.equal(batchDepositedTokenIds); + expect(depositEvent?.args?.amounts).to.deep.equal( + batchDepositedTokenIds.map((tokenId) => ethers.utils.parseUnits(String(tokenId))) + ); + }); + + it("withdraw tokens from child chain with same address", async () => { + const randomAmount = Math.floor(Math.random() * depositedTokenIds[0] + 1); + const depositTx = await childMintableERC1155Predicate.withdraw( + childTokenAddr, + depositedTokenIds[0], + ethers.utils.parseUnits(String(randomAmount)) + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log: any) => log.event === "MintableERC1155Withdraw"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.tokenId).to.equal(depositedTokenIds[0]); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("withdraw tokens from child chain with different address", async () => { + const randomAmount = Math.floor(Math.random() * depositedTokenIds[1] + 1); + const depositTx = await childMintableERC1155Predicate + .connect(accounts[1]) + .withdrawTo( + childTokenAddr, + accounts[0].address, + depositedTokenIds[1], + ethers.utils.parseUnits(String(randomAmount)) + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log: any) => log.event === "MintableERC1155Withdraw"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[1].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.tokenId).to.equal(depositedTokenIds[1]); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("batch withdraw tokens from child chain: success", async () => { + const batchSize = Math.max(1, Math.floor(Math.random() * batchDepositedTokenIds.length)); + const receiverArr = []; + for (let i = 0; i < batchSize; i++) { + receiverArr.push(accounts[1].address); + } + batchDepositedTokenAmounts = batchDepositedTokenIds.map((tokenId) => ethers.utils.parseUnits(String(tokenId))); + const depositTx = await childMintableERC1155Predicate.connect(accounts[2]).withdrawBatch( + childTokenAddr, + receiverArr, + batchDepositedTokenIds.slice(0, batchSize), + batchDepositedTokenIds.slice(0, batchSize).map((tokenId) => ethers.utils.parseUnits(String(tokenId))) + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log: any) => log.event === "MintableERC1155WithdrawBatch"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[2].address); + expect(depositEvent?.args?.receivers).to.deep.equal(receiverArr); + expect(depositEvent?.args?.tokenIds).to.deep.equal(batchDepositedTokenIds.slice(0, batchSize)); + expect(depositEvent?.args?.amounts).to.deep.equal( + batchDepositedTokenIds.slice(0, batchSize).map((tokenId) => ethers.utils.parseUnits(String(tokenId))) + ); + }); + + it("fail deposit tokens: only exit helper", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + 0, + 0, + ] + ); + await expect( + childMintableERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC1155Predicate: ONLY_EXIT_HELPER"); + }); + + it("fail deposit tokens: only root predicate", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + exitHelperChildERC1155Predicate.onL2StateReceive(0, ethers.Wallet.createRandom().address, stateSyncData) + ).to.be.revertedWith("ChildMintableERC1155Predicate: ONLY_ROOT_PREDICATE"); + }); + + it("fail deposit tokens: invalid signature", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256", "uint256"], + [ethers.utils.randomBytes(32), ethers.constants.AddressZero, accounts[0].address, accounts[0].address, 0, 0] + ); + await expect( + exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC1155Predicate: INVALID_SIGNATURE"); + }); + + it("fail deposit tokens of unknown child token: unmapped token", async () => { + let stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + 0, + 0, + ] + ); + await expect( + exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC1155Predicate: UNMAPPED_TOKEN"); + }); + + it("fail withdraw tokens of unknown child token: not a contract", async () => { + await expect(childMintableERC1155Predicate.withdraw(ethers.Wallet.createRandom().address, 0, 1)).to.be.revertedWith( + "ChildMintableERC1155Predicate: NOT_CONTRACT" + ); + await expect( + childMintableERC1155Predicate.withdrawBatch( + ethers.Wallet.createRandom().address, + [ethers.constants.AddressZero], + [0], + [1] + ) + ).to.be.revertedWith("ChildMintableERC1155Predicate: NOT_CONTRACT"); + }); + + it("fail deposit tokens of unknown child token: wrong deposit token", async () => { + childMintableERC1155Predicate.connect(await ethers.getSigner(exitHelper.address)); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + 0, + 0, + ] + ); + await expect( + exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC1155Predicate: UNMAPPED_TOKEN"); + }); + + it("fail deposit tokens of unknown child token: unmapped token", async () => { + const rootToken = ethers.Wallet.createRandom().address; + const childToken = await (await ethers.getContractFactory("ChildERC1155")).deploy(); + await childToken.initialize(rootToken, "TEST"); + let stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + ethers.Wallet.createRandom().address, + accounts[0].address, + accounts[0].address, + 0, + 0, + ] + ); + await expect( + exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC1155Predicate: UNMAPPED_TOKEN"); + stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address[]", "uint256[]", "uint256[]"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT_BATCH"]), + ethers.Wallet.createRandom().address, + accounts[0].address, + [accounts[0].address], + [0], + [0], + ] + ); + await expect( + exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC1155Predicate: UNMAPPED_TOKEN"); + }); + + it("fail withdraw tokens of unknown child token: unmapped token", async () => { + const rootToken = ethers.Wallet.createRandom().address; + const childToken = await (await ethers.getContractFactory("ChildERC1155")).deploy(); + await childToken.initialize(rootToken, "TEST"); + await expect(childMintableERC1155Predicate.withdraw(childToken.address, 0, 1)).to.be.revertedWith( + "ChildMintableERC1155Predicate: UNMAPPED_TOKEN" + ); + await expect( + exitHelperChildERC1155Predicate.withdrawBatch(childToken.address, [ethers.constants.AddressZero], [0], [1]) + ).to.be.revertedWith("ChildMintableERC1155Predicate: UNMAPPED_TOKEN"); + }); + + // since we fake NativeERC20 here, keep this function last: + it("fail deposit tokens: mint failed", async () => { + let stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + rootToken, + accounts[0].address, + accounts[0].address, + 1, + 1, + ] + ); + const fakeERC1155 = await smock.fake("ChildERC1155", { + address: childTokenAddr, + }); + fakeERC1155.supportsInterface.returns(true); + fakeERC1155.rootToken.returns(rootToken); + fakeERC1155.predicate.returns(childMintableERC1155Predicate.address); + fakeERC1155.mint.returns(false); + await expect( + exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC1155Predicate: MINT_FAILED"); + stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address[]", "uint256[]", "uint256[]"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT_BATCH"]), + rootToken, + accounts[0].address, + [accounts[0].address], + [1], + [1], + ] + ); + fakeERC1155.mintBatch.returns(false); + await expect( + exitHelperChildERC1155Predicate.onL2StateReceive(0, rootERC1155Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC1155Predicate: MINT_FAILED"); + }); + + it("fail withdraw tokens: burn failed", async () => { + const fakeERC1155 = await smock.fake("ChildERC1155", { + address: childTokenAddr, + }); + fakeERC1155.supportsInterface.returns(true); + fakeERC1155.rootToken.returns(rootToken); + fakeERC1155.predicate.returns(childMintableERC1155Predicate.address); + fakeERC1155.burn.returns(false); + await expect(childMintableERC1155Predicate.withdraw(childTokenAddr, 0, 1)).to.be.revertedWith( + "ChildMintableERC1155Predicate: BURN_FAILED" + ); + fakeERC1155.burnBatch.returns(false); + await expect( + childMintableERC1155Predicate.withdrawBatch(childTokenAddr, [ethers.constants.AddressZero], [0], [1]) + ).to.be.revertedWith("ChildMintableERC1155Predicate: BURN_FAILED"); + }); + + it("fail withdraw tokens: bad interface", async () => { + const fakeERC1155 = await smock.fake("ChildERC1155", { + address: childTokenAddr, + }); + fakeERC1155.supportsInterface.reverts(); + await expect(childMintableERC1155Predicate.withdraw(childTokenAddr, 0, 1)).to.be.revertedWith( + "ChildMintableERC1155Predicate: NOT_CONTRACT" + ); + }); +}); diff --git a/test/root/ChildMintableERC20Predicate.test.ts b/test/root/ChildMintableERC20Predicate.test.ts new file mode 100644 index 00000000..b8852644 --- /dev/null +++ b/test/root/ChildMintableERC20Predicate.test.ts @@ -0,0 +1,386 @@ +import { expect } from "chai"; +import { ethers, network } from "hardhat"; +import { + ChildMintableERC20Predicate, + ChildMintableERC20Predicate__factory, + StateSender, + StateSender__factory, + ExitHelper, + ExitHelper__factory, + ChildERC20, + ChildERC20__factory, + MockERC20, +} from "../../typechain-types"; +import { setBalance, impersonateAccount, stopImpersonatingAccount } from "@nomicfoundation/hardhat-network-helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { smock } from "@defi-wonderland/smock"; +import { alwaysTrueBytecode } from "../constants"; + +describe("ChildMintableERC20Predicate", () => { + let childMintableERC20Predicate: ChildMintableERC20Predicate, + exitHelperChildMintableERC20Predicate: ChildMintableERC20Predicate, + stateSender: StateSender, + exitHelper: ExitHelper, + rootERC20Predicate: string, + rootToken: string, + childTokenTemplate: ChildERC20, + childToken: ChildERC20, + totalSupply: number = 0, + accounts: SignerWithAddress[]; + before(async () => { + accounts = await ethers.getSigners(); + + const StateSender: StateSender__factory = await ethers.getContractFactory("StateSender"); + stateSender = await StateSender.deploy(); + + await stateSender.deployed(); + + const ExitHelper: ExitHelper__factory = await ethers.getContractFactory("ExitHelper"); + exitHelper = await ExitHelper.deploy(); + + await exitHelper.deployed(); + + rootERC20Predicate = ethers.Wallet.createRandom().address; + rootToken = ethers.Wallet.createRandom().address; + + const ChildERC20: ChildERC20__factory = await ethers.getContractFactory("ChildERC20"); + childTokenTemplate = await ChildERC20.deploy(); + + await childTokenTemplate.deployed(); + + const ChildMintableERC20Predicate: ChildMintableERC20Predicate__factory = await ethers.getContractFactory( + "ChildMintableERC20Predicate" + ); + childMintableERC20Predicate = await ChildMintableERC20Predicate.deploy(); + + await childMintableERC20Predicate.deployed(); + + impersonateAccount(exitHelper.address); + setBalance(exitHelper.address, "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + exitHelperChildMintableERC20Predicate = childMintableERC20Predicate.connect( + await ethers.getSigner(exitHelper.address) + ); + }); + + it("fail bad initialization", async () => { + await expect( + childMintableERC20Predicate.initialize( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ) + ).to.be.revertedWith("ChildMintableERC20Predicate: BAD_INITIALIZATION"); + }); + + it("initialize and validate initialization", async () => { + await childMintableERC20Predicate.initialize( + stateSender.address, + exitHelper.address, + rootERC20Predicate, + childTokenTemplate.address + ); + expect(await childMintableERC20Predicate.stateSender()).to.equal(stateSender.address); + expect(await childMintableERC20Predicate.exitHelper()).to.equal(exitHelper.address); + expect(await childMintableERC20Predicate.rootERC20Predicate()).to.equal(rootERC20Predicate); + expect(await childMintableERC20Predicate.childTokenTemplate()).to.equal(childTokenTemplate.address); + }); + + it("fail reinitialization", async () => { + await expect( + childMintableERC20Predicate.initialize( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ) + ).to.be.revertedWith("Initializable: contract is already initialized"); + }); + + it("map token success", async () => { + const clonesContract = await (await ethers.getContractFactory("MockClones")).deploy(); + const childTokenAddr = await clonesContract.predictDeterministicAddress( + childTokenTemplate.address, + ethers.utils.solidityKeccak256(["address"], [rootToken]), + childMintableERC20Predicate.address + ); + childToken = childTokenTemplate.attach(childTokenAddr); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "string", "string", "uint8"], + [ethers.utils.solidityKeccak256(["string"], ["MAP_TOKEN"]), rootToken, "TEST1", "TEST1", 18] + ); + const mapTx = await exitHelperChildMintableERC20Predicate.onL2StateReceive(0, rootERC20Predicate, stateSyncData); + const mapReceipt = await mapTx.wait(); + const mapEvent = mapReceipt?.events?.find((log) => log.event === "MintableTokenMapped"); + expect(mapEvent?.args?.rootToken).to.equal(rootToken); + expect(mapEvent?.args?.childToken).to.equal(childTokenAddr); + expect(await childToken.predicate()).to.equal(childMintableERC20Predicate.address); + expect(await childToken.rootToken()).to.equal(rootToken); + expect(await childToken.name()).to.equal("TEST1"); + expect(await childToken.symbol()).to.equal("TEST1"); + expect(await childToken.decimals()).to.equal(18); + }); + + it("map token fail: invalid root token", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "string", "string", "uint8"], + [ + ethers.utils.solidityKeccak256(["string"], ["MAP_TOKEN"]), + "0x0000000000000000000000000000000000000000", + "TEST1", + "TEST1", + 18, + ] + ); + await expect( + exitHelperChildMintableERC20Predicate.onL2StateReceive(0, rootERC20Predicate, stateSyncData) + ).to.be.revertedWithPanic(); + }); + + it("map token fail: already mapped", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "string", "string", "uint8"], + [ethers.utils.solidityKeccak256(["string"], ["MAP_TOKEN"]), rootToken, "TEST1", "TEST1", 18] + ); + await expect( + exitHelperChildMintableERC20Predicate.onL2StateReceive(0, rootERC20Predicate, stateSyncData) + ).to.be.revertedWithPanic(); + }); + + it("deposit tokens from root chain with same address", async () => { + const randomAmount = Math.floor(Math.random() * 1000000 + 100); + totalSupply += randomAmount; + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + rootToken, + accounts[0].address, + accounts[0].address, + ethers.utils.parseUnits(String(randomAmount)), + ] + ); + const depositTx = await exitHelperChildMintableERC20Predicate.onL2StateReceive( + 0, + rootERC20Predicate, + stateSyncData + ); + const depositReceipt = await depositTx.wait(); + stopImpersonatingAccount(exitHelperChildMintableERC20Predicate.address); + const depositEvent = depositReceipt.events?.find((log) => log.event === "MintableERC20Deposit"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childToken.address); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("deposit tokens from root chain with different address", async () => { + const randomAmount = Math.floor(Math.random() * 1000000 + 100); + totalSupply += randomAmount; + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + rootToken, + accounts[0].address, + accounts[1].address, + ethers.utils.parseUnits(String(randomAmount)), + ] + ); + const depositTx = await exitHelperChildMintableERC20Predicate.onL2StateReceive( + 0, + rootERC20Predicate, + stateSyncData + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log) => log.event === "MintableERC20Deposit"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childToken.address); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[1].address); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("withdraw tokens from child chain with same address", async () => { + const randomAmount = Math.floor(Math.random() * 100 + 1); + totalSupply -= randomAmount; + const depositTx = await childMintableERC20Predicate.withdraw( + childToken.address, + ethers.utils.parseUnits(String(randomAmount)) + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log: any) => log.event === "MintableERC20Withdraw"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childToken.address); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("withdraw tokens from child chain with different address", async () => { + const randomAmount = Math.floor(Math.random() * 100 + 1); + totalSupply -= randomAmount; + const depositTx = await childMintableERC20Predicate.withdrawTo( + childToken.address, + accounts[1].address, + ethers.utils.parseUnits(String(randomAmount)) + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log: any) => log.event === "MintableERC20Withdraw"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childToken.address); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[1].address); + expect(depositEvent?.args?.amount).to.equal(ethers.utils.parseUnits(String(randomAmount))); + }); + + it("fail deposit tokens: only state receiver", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect(childMintableERC20Predicate.onL2StateReceive(0, rootERC20Predicate, stateSyncData)).to.be.revertedWith( + "ChildMintableERC20Predicate: ONLY_STATE_RECEIVER" + ); + }); + + it("fail deposit tokens: only root predicate", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + exitHelperChildMintableERC20Predicate.onL2StateReceive(0, ethers.Wallet.createRandom().address, stateSyncData) + ).to.be.revertedWith("ChildMintableERC20Predicate: ONLY_ROOT_PREDICATE"); + }); + + it("fail deposit tokens: invalid signature", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ethers.utils.randomBytes(32), rootToken, childToken.address, accounts[0].address, accounts[0].address, 1] + ); + await expect( + exitHelperChildMintableERC20Predicate.onL2StateReceive(0, rootERC20Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC20Predicate: INVALID_SIGNATURE"); + }); + + it("fail deposit tokens of unknown child token: not a contract", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + exitHelperChildMintableERC20Predicate.onL2StateReceive(0, rootERC20Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC20Predicate: UNMAPPED_TOKEN"); + }); + + it("fail withdraw tokens of unknown child token: not a contract", async () => { + await expect(childMintableERC20Predicate.withdraw(ethers.Wallet.createRandom().address, 1)).to.be.revertedWith( + "ChildMintableERC20Predicate: NOT_CONTRACT" + ); + }); + + it("fail deposit tokens of unknown child token: wrong deposit token", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + rootToken, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + exitHelperChildMintableERC20Predicate.onL2StateReceive(0, rootERC20Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC20Predicate: UNMAPPED_TOKEN"); + }); + + it("fail deposit tokens of unknown child token: unmapped token", async () => { + const rootToken = ethers.Wallet.createRandom().address; + const childToken = await (await ethers.getContractFactory("ChildERC20")).deploy(); + await childToken.initialize(rootToken, "TEST", "TEST", 18); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + rootToken, + childToken.address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + exitHelperChildMintableERC20Predicate.onL2StateReceive(0, rootERC20Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC20Predicate: UNMAPPED_TOKEN"); + }); + + it("fail withdraw tokens of unknown child token: unmapped token", async () => { + const rootToken = ethers.Wallet.createRandom().address; + const childToken = await (await ethers.getContractFactory("ChildERC20")).deploy(); + await childToken.initialize(rootToken, "TEST", "TEST", 18); + await expect(childMintableERC20Predicate.withdraw(childToken.address, 1)).to.be.revertedWith( + "ChildMintableERC20Predicate: UNMAPPED_TOKEN" + ); + }); + + // since we fake NativeERC20 here, keep this function last: + it("fail deposit tokens: mint failed", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + rootToken, + childToken.address, + accounts[0].address, + accounts[0].address, + 1, + ] + ); + const fakeNativeERC20 = await smock.fake("ChildERC20", { + address: childToken.address, + }); + fakeNativeERC20.rootToken.returns(rootToken); + fakeNativeERC20.predicate.returns(childMintableERC20Predicate.address); + fakeNativeERC20.mint.returns(false); + await expect( + exitHelperChildMintableERC20Predicate.onL2StateReceive(0, rootERC20Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC20Predicate: MINT_FAILED"); + fakeNativeERC20.mint.returns(); + }); + + it("fail withdraw tokens: burn failed", async () => { + const fakeNativeERC20 = await smock.fake("ChildERC20", { + address: childToken.address, + }); + fakeNativeERC20.rootToken.returns(rootToken); + fakeNativeERC20.predicate.returns(childMintableERC20Predicate.address); + fakeNativeERC20.burn.returns(false); + await expect(exitHelperChildMintableERC20Predicate.withdraw(childToken.address, 1)).to.be.revertedWith( + "ChildMintableERC20Predicate: BURN_FAILED" + ); + }); +}); diff --git a/test/root/ChildMintableERC721Predicate.test.ts b/test/root/ChildMintableERC721Predicate.test.ts new file mode 100644 index 00000000..8f55badf --- /dev/null +++ b/test/root/ChildMintableERC721Predicate.test.ts @@ -0,0 +1,487 @@ +import { expect } from "chai"; +import { ethers, network } from "hardhat"; +import { + ChildMintableERC721Predicate, + ChildMintableERC721Predicate__factory, + StateSender, + StateSender__factory, + ExitHelper, + ExitHelper__factory, + ChildERC721, + ChildERC721__factory, +} from "../../typechain-types"; +import { + setCode, + setBalance, + impersonateAccount, + stopImpersonatingAccount, +} from "@nomicfoundation/hardhat-network-helpers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { smock } from "@defi-wonderland/smock"; + +describe("ChildMintableERC721Predicate", () => { + let childMintableERC721Predicate: ChildMintableERC721Predicate, + exitHelperChildMintableERC721Predicate: ChildMintableERC721Predicate, + stateSender: StateSender, + exitHelper: ExitHelper, + rootERC721Predicate: string, + childERC721: ChildERC721, + rootToken: string, + childTokenAddr: string, + childToken: ChildERC721, + depositedTokenIds: number[] = [], + batchDepositedTokenIds: number[] = [], + accounts: SignerWithAddress[]; + before(async () => { + accounts = await ethers.getSigners(); + + const StateSender: StateSender__factory = await ethers.getContractFactory("StateSender"); + stateSender = await StateSender.deploy(); + + await stateSender.deployed(); + + const ExitHelper: ExitHelper__factory = await ethers.getContractFactory("ExitHelper"); + exitHelper = await ExitHelper.deploy(); + + await exitHelper.deployed(); + + rootERC721Predicate = ethers.Wallet.createRandom().address; + + const ChildERC721: ChildERC721__factory = await ethers.getContractFactory("ChildERC721"); + childERC721 = await ChildERC721.deploy(); + + await childERC721.deployed(); + + const ChildMintableERC721Predicate: ChildMintableERC721Predicate__factory = await ethers.getContractFactory( + "ChildMintableERC721Predicate" + ); + childMintableERC721Predicate = await ChildMintableERC721Predicate.deploy(); + + await childMintableERC721Predicate.deployed(); + + impersonateAccount(exitHelper.address); + setBalance(exitHelper.address, "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + exitHelperChildMintableERC721Predicate = childMintableERC721Predicate.connect( + await ethers.getSigner(exitHelper.address) + ); + }); + + it("fail bad initialization", async () => { + await expect( + childMintableERC721Predicate.initialize( + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ) + ).to.be.revertedWith("ChildMintableERC721Predicate: BAD_INITIALIZATION"); + }); + + it("initialize and validate initialization", async () => { + await childMintableERC721Predicate.initialize( + stateSender.address, + exitHelper.address, + rootERC721Predicate, + childERC721.address + ); + expect(await childMintableERC721Predicate.stateSender()).to.equal(stateSender.address); + expect(await childMintableERC721Predicate.exitHelper()).to.equal(exitHelper.address); + expect(await childMintableERC721Predicate.rootERC721Predicate()).to.equal(rootERC721Predicate); + expect(await childMintableERC721Predicate.childTokenTemplate()).to.equal(childERC721.address); + }); + + it("fail reinitialization", async () => { + await expect( + childMintableERC721Predicate.initialize( + stateSender.address, + exitHelper.address, + rootERC721Predicate, + childERC721.address + ) + ).to.be.revertedWith("Initializable: contract is already initialized"); + }); + + it("map token success", async () => { + rootToken = ethers.Wallet.createRandom().address; + const clonesContract = await (await ethers.getContractFactory("MockClones")).deploy(); + childTokenAddr = await clonesContract.predictDeterministicAddress( + childERC721.address, + ethers.utils.solidityKeccak256(["address"], [rootToken]), + childMintableERC721Predicate.address + ); + childToken = childERC721.attach(childTokenAddr); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "string", "string", "uint8"], + [ethers.utils.solidityKeccak256(["string"], ["MAP_TOKEN"]), rootToken, "TEST", "TEST", 18] + ); + const mapTx = await exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData); + const mapReceipt = await mapTx.wait(); + const mapEvent = mapReceipt?.events?.find((log) => log.event === "MintableTokenMapped"); + expect(mapEvent?.args?.rootToken).to.equal(rootToken); + expect(mapEvent?.args?.childToken).to.equal(childTokenAddr); + expect(await childToken.predicate()).to.equal(childMintableERC721Predicate.address); + expect(await childToken.rootToken()).to.equal(rootToken); + expect(await childToken.name()).to.equal("TEST"); + expect(await childToken.symbol()).to.equal("TEST"); + }); + + it("map token fail: invalid root token", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "string", "string", "uint8"], + [ + ethers.utils.solidityKeccak256(["string"], ["MAP_TOKEN"]), + "0x0000000000000000000000000000000000000000", + "TEST", + "TEST", + 18, + ] + ); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWithPanic(); + }); + + it("map token fail: already mapped", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "string", "string", "uint8"], + [ethers.utils.solidityKeccak256(["string"], ["MAP_TOKEN"]), rootToken, "TEST", "TEST", 18] + ); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWithPanic(); + }); + + it("deposit fail: unmapped token", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + ethers.Wallet.createRandom().address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC721Predicate: UNMAPPED_TOKEN"); + }); + + it("deposit tokens from root chain with same address", async () => { + const randomTokenId = Math.floor(Math.random() * 1000000 + 1); + depositedTokenIds.push(randomTokenId); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + rootToken, + accounts[0].address, + accounts[0].address, + randomTokenId, + ] + ); + const depositTx = await exitHelperChildMintableERC721Predicate.onL2StateReceive( + 0, + rootERC721Predicate, + stateSyncData + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log) => log.event === "MintableERC721Deposit"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.tokenId).to.equal(randomTokenId); + }); + + it("deposit tokens from root chain with different address", async () => { + const randomTokenId = Math.floor(Math.random() * 1000000 + 1); + depositedTokenIds.push(randomTokenId); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + rootToken, + accounts[0].address, + accounts[1].address, + randomTokenId, + ] + ); + const depositTx = await exitHelperChildMintableERC721Predicate.onL2StateReceive( + 0, + rootERC721Predicate, + stateSyncData + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log) => log.event === "MintableERC721Deposit"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[1].address); + expect(depositEvent?.args?.tokenId).to.equal(randomTokenId); + }); + + it("deposit batch tokens from root chain", async () => { + const batchSize = Math.floor(Math.random() * 10 + 1); + const receiverArr = []; + for (let i = 0; i < batchSize; i++) { + const randomTokenId = Math.floor(Math.random() * 1000000 + 1); + batchDepositedTokenIds.push(randomTokenId); + receiverArr.push(accounts[2].address); + } + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address[]", "uint256[]"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT_BATCH"]), + rootToken, + accounts[0].address, + receiverArr, + batchDepositedTokenIds, + ] + ); + const depositTx = await exitHelperChildMintableERC721Predicate.onL2StateReceive( + 0, + rootERC721Predicate, + stateSyncData + ); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log) => log.event === "MintableERC721DepositBatch"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receivers).to.deep.equal(receiverArr); + expect(depositEvent?.args?.tokenIds).to.deep.equal(batchDepositedTokenIds); + }); + + it("withdraw tokens from child chain with same address", async () => { + const depositTx = await childMintableERC721Predicate.withdraw(childTokenAddr, depositedTokenIds[0]); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log: any) => log.event === "MintableERC721Withdraw"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[0].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.tokenId).to.equal(depositedTokenIds[0]); + }); + + it("withdraw tokens from child chain with different address", async () => { + const depositTx = await childMintableERC721Predicate + .connect(accounts[1]) + .withdrawTo(childTokenAddr, accounts[0].address, depositedTokenIds[1]); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log: any) => log.event === "MintableERC721Withdraw"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[1].address); + expect(depositEvent?.args?.receiver).to.equal(accounts[0].address); + expect(depositEvent?.args?.tokenId).to.equal(depositedTokenIds[1]); + }); + + it("withdraw batch tokens fail: not contract", async () => { + await expect( + childMintableERC721Predicate.withdrawBatch( + ethers.Wallet.createRandom().address, + [ethers.constants.AddressZero], + [0] + ) + ).to.be.revertedWith("ChildMintableERC721Predicate: NOT_CONTRACT"); + }); + + it("withdraw batch tokens from child chain", async () => { + const batchSize = Math.floor(Math.random() * (await childToken.balanceOf(accounts[2].address)).toNumber() + 1); + const receiverArr = []; + for (let i = 0; i < batchSize; i++) { + receiverArr.push(ethers.Wallet.createRandom().address); + } + const depositTx = await exitHelperChildMintableERC721Predicate + .connect(accounts[2]) + .withdrawBatch(childTokenAddr, receiverArr, batchDepositedTokenIds.slice(0, batchSize)); + const depositReceipt = await depositTx.wait(); + const depositEvent = depositReceipt.events?.find((log: any) => log.event === "MintableERC721WithdrawBatch"); + expect(depositEvent?.args?.rootToken).to.equal(rootToken); + expect(depositEvent?.args?.childToken).to.equal(childTokenAddr); + expect(depositEvent?.args?.sender).to.equal(accounts[2].address); + expect(depositEvent?.args?.receivers).to.deep.equal(receiverArr); + expect(depositEvent?.args?.tokenIds).to.deep.equal(batchDepositedTokenIds.slice(0, batchSize)); + }); + + it("fail deposit tokens: only exit helper", async () => { + const tempChildMintableERC721Predicate = childMintableERC721Predicate.connect(accounts[19]); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + tempChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC721Predicate: ONLY_EXIT_HELPER"); + }); + + it("fail deposit tokens: only root predicate", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, ethers.Wallet.createRandom().address, stateSyncData) + ).to.be.revertedWith("ChildMintableERC721Predicate: ONLY_ROOT_PREDICATE"); + }); + + it("fail deposit tokens: invalid signature", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ + ethers.utils.randomBytes(32), + ethers.constants.AddressZero, + ethers.constants.AddressZero, + accounts[0].address, + accounts[0].address, + 1, + ] + ); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC721Predicate: INVALID_SIGNATURE"); + }); + + it("fail deposit tokens of unknown child token: not a contract", async () => { + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + "0x0000000000000000000000000000000000000000", + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC721Predicate: UNMAPPED_TOKEN"); + }); + + it("fail withdraw tokens of unknown child token: not a contract", async () => { + await expect(childMintableERC721Predicate.withdraw(ethers.Wallet.createRandom().address, 1)).to.be.revertedWith( + "ChildMintableERC721Predicate: NOT_CONTRACT" + ); + }); + + it("fail deposit tokens of unknown child token: wrong deposit token", async () => { + childMintableERC721Predicate.connect(await ethers.getSigner(exitHelper.address)); + const stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "address", "uint256"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), + ethers.constants.AddressZero, + childTokenAddr, + accounts[0].address, + accounts[0].address, + 0, + ] + ); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC721Predicate: UNMAPPED_TOKEN"); + }); + + it("fail deposit tokens of unknown child token: unmapped token", async () => { + const rootToken = ethers.Wallet.createRandom().address; + const childToken = await (await ethers.getContractFactory("ChildERC721")).deploy(); + await childToken.initialize(rootToken, "TEST", "TEST"); + let stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), rootToken, accounts[0].address, accounts[0].address, 0] + ); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC721Predicate: UNMAPPED_TOKEN"); + stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address[]", "uint256[]"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT_BATCH"]), + rootToken, + accounts[0].address, + [accounts[0].address], + [0], + ] + ); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC721Predicate: UNMAPPED_TOKEN"); + }); + + it("fail withdraw tokens of unknown child token: unmapped token", async () => { + const rootToken = ethers.Wallet.createRandom().address; + const childToken = await (await ethers.getContractFactory("ChildERC721")).deploy(); + await childToken.initialize(rootToken, "TEST", "TEST"); + await expect(exitHelperChildMintableERC721Predicate.withdraw(childToken.address, 0)).to.be.revertedWith( + "ChildMintableERC721Predicate: UNMAPPED_TOKEN" + ); + await expect( + childMintableERC721Predicate.withdrawBatch(childToken.address, [ethers.constants.AddressZero], [0]) + ).to.be.revertedWith("ChildMintableERC721Predicate: UNMAPPED_TOKEN"); + }); + + // since we fake ChildERC721 here, keep this function last: + it("fail deposit tokens: mint failed", async () => { + let stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address", "uint256"], + [ethers.utils.solidityKeccak256(["string"], ["DEPOSIT"]), rootToken, accounts[0].address, accounts[0].address, 1] + ); + const fakeChildERC721 = await smock.fake("ChildERC721", { + address: childTokenAddr, + }); + fakeChildERC721.supportsInterface.returns(true); + fakeChildERC721.rootToken.returns(rootToken); + fakeChildERC721.predicate.returns(childMintableERC721Predicate.address); + fakeChildERC721.mint.returns(false); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC721Predicate: MINT_FAILED"); + stateSyncData = ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "address", "address[]", "uint256[]"], + [ + ethers.utils.solidityKeccak256(["string"], ["DEPOSIT_BATCH"]), + rootToken, + accounts[0].address, + [accounts[0].address], + [1], + ] + ); + fakeChildERC721.mintBatch.returns(false); + await expect( + exitHelperChildMintableERC721Predicate.onL2StateReceive(0, rootERC721Predicate, stateSyncData) + ).to.be.revertedWith("ChildMintableERC721Predicate: MINT_FAILED"); + fakeChildERC721.mint.returns(); + }); + + it("fail withdraw tokens: burn failed", async () => { + const fakeChildERC721 = await smock.fake("ChildERC721", { + address: childTokenAddr, + }); + fakeChildERC721.supportsInterface.returns(true); + fakeChildERC721.rootToken.returns(rootToken); + fakeChildERC721.predicate.returns(childMintableERC721Predicate.address); + fakeChildERC721.burn.returns(false); + await expect(exitHelperChildMintableERC721Predicate.withdraw(childTokenAddr, 1)).to.be.revertedWith( + "ChildMintableERC721Predicate: BURN_FAILED" + ); + fakeChildERC721.burnBatch.returns(false); + await expect( + childMintableERC721Predicate.withdrawBatch(childTokenAddr, [accounts[0].address], [1]) + ).to.be.revertedWith("ChildMintableERC721Predicate: BURN_FAILED"); + }); +}); From 8da70d71f7e7a3407f933f98dc770eab362968ad Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Mon, 26 Jun 2023 08:42:38 +1000 Subject: [PATCH 2/6] Convert StakeManagerLib and ChildChainLib to abstract contracts. Add storage gaps for these contracts --- contracts/lib/ChildManagerLib.sol | 27 --------- contracts/lib/StakeManagerLib.sol | 51 ---------------- contracts/root/staking/StakeManager.sol | 39 ++++++------ .../root/staking/StakeManagerChildData.sol | 52 ++++++++++++++++ .../root/staking/StakeManagerStakingData.sol | 60 +++++++++++++++++++ docs/child/RootMintableERC1155Predicate.md | 6 +- docs/child/RootMintableERC20Predicate.md | 2 +- docs/child/RootMintableERC721Predicate.md | 4 +- docs/lib/ChildManagerLib.md | 12 ---- docs/lib/StakeManagerLib.md | 12 ---- docs/root/ChildMintableERC1155Predicate.md | 6 +- docs/root/ChildMintableERC20Predicate.md | 4 +- docs/root/ChildMintableERC721Predicate.md | 4 +- docs/root/staking/StakeManagerChildData.md | 12 ++++ docs/root/staking/StakeManagerStakingData.md | 12 ++++ package-lock.json | 42 +++++++++---- package.json | 2 +- 17 files changed, 197 insertions(+), 150 deletions(-) delete mode 100644 contracts/lib/ChildManagerLib.sol delete mode 100644 contracts/lib/StakeManagerLib.sol create mode 100644 contracts/root/staking/StakeManagerChildData.sol create mode 100644 contracts/root/staking/StakeManagerStakingData.sol delete mode 100644 docs/lib/ChildManagerLib.md delete mode 100644 docs/lib/StakeManagerLib.md create mode 100644 docs/root/staking/StakeManagerChildData.md create mode 100644 docs/root/staking/StakeManagerStakingData.md diff --git a/contracts/lib/ChildManagerLib.sol b/contracts/lib/ChildManagerLib.sol deleted file mode 100644 index 4f5e7c08..00000000 --- a/contracts/lib/ChildManagerLib.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -struct ChildChains { - uint256 counter; - mapping(uint256 => address) managers; - mapping(address => uint256) ids; -} - -library ChildManagerLib { - function registerChild(ChildChains storage self, address manager) internal returns (uint256 id) { - assert(manager != address(0)); - id = ++self.counter; - self.managers[id] = manager; - self.ids[manager] = id; - } - - function managerOf(ChildChains storage self, uint256 id) internal view returns (address manager) { - manager = self.managers[id]; - require(manager != address(0), "Invalid id"); - } - - function idFor(ChildChains storage self, address manager) internal view returns (uint256 id) { - id = self.ids[manager]; - require(id != 0, "Invalid manager"); - } -} diff --git a/contracts/lib/StakeManagerLib.sol b/contracts/lib/StakeManagerLib.sol deleted file mode 100644 index 6148274c..00000000 --- a/contracts/lib/StakeManagerLib.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -import "../interfaces/root/staking/IStakeManager.sol"; - -struct Stakes { - uint256 totalStake; - // validator => child => amount - mapping(address => mapping(uint256 => uint256)) stakes; - // child chain => total stake - mapping(uint256 => uint256) totalStakePerChild; - mapping(address => uint256) totalStakes; - mapping(address => uint256) withdrawableStakes; -} - -library StakeManagerLib { - function addStake(Stakes storage self, address validator, uint256 id, uint256 amount) internal { - self.stakes[validator][id] += amount; - self.totalStakePerChild[id] += amount; - self.totalStakes[validator] += amount; - self.totalStake += amount; - } - - function removeStake(Stakes storage self, address validator, uint256 id, uint256 amount) internal { - self.stakes[validator][id] -= amount; - self.totalStakePerChild[id] -= amount; - self.totalStakes[validator] -= amount; - self.totalStake -= amount; - self.withdrawableStakes[validator] += amount; - } - - function withdrawStake(Stakes storage self, address validator, uint256 amount) internal { - self.withdrawableStakes[validator] -= amount; - } - - function withdrawableStakeOf(Stakes storage self, address validator) internal view returns (uint256 amount) { - amount = self.withdrawableStakes[validator]; - } - - function totalStakeOfChild(Stakes storage self, uint256 id) internal view returns (uint256 amount) { - amount = self.totalStakePerChild[id]; - } - - function stakeOf(Stakes storage self, address validator, uint256 id) internal view returns (uint256 amount) { - amount = self.stakes[validator][id]; - } - - function totalStakeOf(Stakes storage self, address validator) internal view returns (uint256 amount) { - amount = self.totalStakes[validator]; - } -} diff --git a/contracts/root/staking/StakeManager.sol b/contracts/root/staking/StakeManager.sol index affc4d6b..1d3cb0cf 100644 --- a/contracts/root/staking/StakeManager.sol +++ b/contracts/root/staking/StakeManager.sol @@ -4,17 +4,14 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "../../lib/ChildManagerLib.sol"; -import "../../lib/StakeManagerLib.sol"; +import "../../interfaces/root/staking/IStakeManager.sol"; +import "./StakeManagerChildData.sol"; +import "./StakeManagerStakingData.sol"; -contract StakeManager is IStakeManager, Initializable { - using ChildManagerLib for ChildChains; - using StakeManagerLib for Stakes; +contract StakeManager is IStakeManager, Initializable, StakeManagerChildData, StakeManagerStakingData { using SafeERC20 for IERC20; IERC20 internal matic; - ChildChains private _chains; - Stakes private _stakes; function initialize(address newMatic) public initializer { matic = IERC20(newMatic); @@ -24,7 +21,7 @@ contract StakeManager is IStakeManager, Initializable { * @inheritdoc IStakeManager */ function registerChildChain(address manager) external returns (uint256 id) { - id = _chains.registerChild(manager); + id = _registerChild(manager); ISupernetManager(manager).onInit(id); // slither-disable-next-line reentrancy-events emit ChildManagerRegistered(id, manager); @@ -34,12 +31,12 @@ contract StakeManager is IStakeManager, Initializable { * @inheritdoc IStakeManager */ function stakeFor(uint256 id, uint256 amount) external { - require(id != 0 && id <= _chains.counter, "INVALID_ID"); + require(id != 0 && id <= counter, "INVALID_ID"); // slither-disable-next-line reentrancy-benign,reentrancy-events matic.safeTransferFrom(msg.sender, address(this), amount); // calling the library directly once fixes the coverage issue // https://github.com/foundry-rs/foundry/issues/4854#issuecomment-1528897219 - StakeManagerLib.addStake(_stakes, msg.sender, id, amount); + _addStake(msg.sender, id, amount); ISupernetManager manager = managerOf(id); manager.onStake(msg.sender, amount); // slither-disable-next-line reentrancy-events @@ -51,7 +48,7 @@ contract StakeManager is IStakeManager, Initializable { */ function releaseStakeOf(address validator, uint256 amount) external { uint256 id = idFor(msg.sender); - _stakes.removeStake(validator, id, amount); + _removeStake(validator, id, amount); // slither-disable-next-line reentrancy-events emit StakeRemoved(id, validator, amount); } @@ -68,9 +65,9 @@ contract StakeManager is IStakeManager, Initializable { */ function slashStakeOf(address validator, uint256 amount) external { uint256 id = idFor(msg.sender); - uint256 stake = stakeOf(validator, id); + uint256 stake = _stakeOf(validator, id); if (amount > stake) amount = stake; - _stakes.removeStake(validator, id, stake); + _removeStake(validator, id, stake); _withdrawStake(validator, msg.sender, amount); emit StakeRemoved(id, validator, stake); emit ValidatorSlashed(id, validator, amount); @@ -80,53 +77,53 @@ contract StakeManager is IStakeManager, Initializable { * @inheritdoc IStakeManager */ function withdrawableStake(address validator) external view returns (uint256 amount) { - amount = _stakes.withdrawableStakeOf(validator); + amount = _withdrawableStakeOf(validator); } /** * @inheritdoc IStakeManager */ function totalStake() external view returns (uint256 amount) { - amount = _stakes.totalStake; + amount = theTotalStake; } /** * @inheritdoc IStakeManager */ function totalStakeOfChild(uint256 id) external view returns (uint256 amount) { - amount = _stakes.totalStakeOfChild(id); + amount = _totalStakeOfChild(id); } /** * @inheritdoc IStakeManager */ function totalStakeOf(address validator) external view returns (uint256 amount) { - amount = _stakes.totalStakeOf(validator); + amount = _totalStakeOf(validator); } /** * @inheritdoc IStakeManager */ function stakeOf(address validator, uint256 id) public view returns (uint256 amount) { - amount = _stakes.stakeOf(validator, id); + amount = _stakeOf(validator, id); } /** * @inheritdoc IStakeManager */ function managerOf(uint256 id) public view returns (ISupernetManager manager) { - manager = ISupernetManager(_chains.managerOf(id)); + manager = ISupernetManager(_managerOf(id)); } /** * @inheritdoc IStakeManager */ function idFor(address manager) public view returns (uint256 id) { - id = ChildManagerLib.idFor(_chains, manager); + id = _idFor(manager); } function _withdrawStake(address validator, address to, uint256 amount) private { - _stakes.withdrawStake(validator, amount); + _withdrawStake(validator, amount); // slither-disable-next-line reentrancy-events matic.safeTransfer(to, amount); emit StakeWithdrawn(validator, to, amount); diff --git a/contracts/root/staking/StakeManagerChildData.sol b/contracts/root/staking/StakeManagerChildData.sol new file mode 100644 index 00000000..e7d914cf --- /dev/null +++ b/contracts/root/staking/StakeManagerChildData.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +/** + * @title StakeManagerChildData + * @notice Holds data to allow look-up between child chain manager contract address and child chain id. + * Note that this is contract is designed to be included in StakeManager. It is upgradeable. + */ +abstract contract StakeManagerChildData { + // Highest child chain id allocated thus far. Child chain id 0x00 is an invalid id. + uint256 internal counter; + // child chain id to child chain manager contract address. + mapping(uint256 => address) private managers; + // child chain manager contract address to child chain id. + mapping(address => uint256) private ids; + + // Storage gap + // solhint-disable-next-line var-name-mixedcase + uint256[1000] private __StorageGapStakeManagerChildData; + + /** + * @notice Register a child chain manager contract and allocate a child chain id. + * @param manager Child chain manager contract address. + * @return id Child chain id allocated for the child chain. + */ + function _registerChild(address manager) internal returns (uint256 id) { + assert(manager != address(0)); + id = ++counter; + managers[id] = manager; + ids[manager] = id; + } + + /** + * @notice Get the child chain manager contract that corresponds to a child chain id. + * @param id Child chain id. + * @return manager Child chain manager contract address. + */ + function _managerOf(uint256 id) internal view returns (address manager) { + manager = managers[id]; + require(manager != address(0), "Invalid id"); + } + + /** + * @notice Get the child chain id that corresponds to a child chain manager contract. + * @param manager Child chain manager contract address. + * @return id Child chain id. + */ + function _idFor(address manager) internal view returns (uint256 id) { + id = ids[manager]; + require(id != 0, "Invalid manager"); + } +} diff --git a/contracts/root/staking/StakeManagerStakingData.sol b/contracts/root/staking/StakeManagerStakingData.sol new file mode 100644 index 00000000..a080d5ab --- /dev/null +++ b/contracts/root/staking/StakeManagerStakingData.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + + +/** + * @title StakeManagerStakingData + * @notice Holds all staking related data. + * Note that this is contract is designed to be included in StakeManager. It is upgradeable. + */ +abstract contract StakeManagerStakingData { + uint256 internal theTotalStake; + // validator => child => amount + mapping(address => mapping(uint256 => uint256)) private stakes; + // child chain id => total stake + mapping(uint256 => uint256) private totalStakePerChild; + // validator address => stake across all child chains. + mapping(address => uint256) private totalStakes; + // validator address => withdrawable stake. + mapping(address => uint256) private withdrawableStakes; + + // Storage gap + // solhint-disable-next-line var-name-mixedcase + uint256[1000] private __StorageGapStakeManagerStakingData; + + + function _addStake(address validator, uint256 id, uint256 amount) internal { + stakes[validator][id] += amount; + totalStakePerChild[id] += amount; + totalStakes[validator] += amount; + theTotalStake += amount; + } + + function _removeStake(address validator, uint256 id, uint256 amount) internal { + stakes[validator][id] -= amount; + totalStakePerChild[id] -= amount; + totalStakes[validator] -= amount; + theTotalStake -= amount; + withdrawableStakes[validator] += amount; + } + + function _withdrawStake(address validator, uint256 amount) internal { + withdrawableStakes[validator] -= amount; + } + + function _withdrawableStakeOf(address validator) internal view returns (uint256 amount) { + amount = withdrawableStakes[validator]; + } + + function _totalStakeOfChild(uint256 id) internal view returns (uint256 amount) { + amount = totalStakePerChild[id]; + } + + function _stakeOf(address validator, uint256 id) internal view returns (uint256 amount) { + amount = stakes[validator][id]; + } + + function _totalStakeOf(address validator) internal view returns (uint256 amount) { + amount = totalStakes[validator]; + } +} diff --git a/docs/child/RootMintableERC1155Predicate.md b/docs/child/RootMintableERC1155Predicate.md index 56352390..81848d2b 100644 --- a/docs/child/RootMintableERC1155Predicate.md +++ b/docs/child/RootMintableERC1155Predicate.md @@ -191,7 +191,7 @@ Function to deposit tokens from the depositor to another address on the child ch function initialize(address newL2StateSender, address newStateReceiver, address newChildERC1155Predicate, address newChildTokenTemplate) external nonpayable ``` -Initilization function for RootERC1155Predicate +Initilization function for RootMintableERC1155Predicate *Can only be called once.* @@ -199,8 +199,8 @@ Initilization function for RootERC1155Predicate | Name | Type | Description | |---|---|---| -| newL2StateSender | address | Address of StateSender to send deposit information to | -| newStateReceiver | address | Address of ExitHelper to receive withdrawal information from | +| newL2StateSender | address | Address of L2StateSender to send deposit information to | +| newStateReceiver | address | Address of StateReceiver to receive withdrawal information from | | newChildERC1155Predicate | address | Address of child ERC1155 predicate to communicate with | | newChildTokenTemplate | address | undefined | diff --git a/docs/child/RootMintableERC20Predicate.md b/docs/child/RootMintableERC20Predicate.md index 7f33cb94..254fe8a7 100644 --- a/docs/child/RootMintableERC20Predicate.md +++ b/docs/child/RootMintableERC20Predicate.md @@ -299,7 +299,7 @@ Initilization function for RootMintableERC20Predicate |---|---|---| | newL2StateSender | address | Address of L2StateSender to send exit information to | | newStateReceiver | address | Address of StateReceiver to receive deposit information from | -| newChildERC20Predicate | address | Address of root ERC20 predicate to communicate with | +| newChildERC20Predicate | address | Address of child ERC20 predicate to communicate with | | newChildTokenTemplate | address | Address of child token implementation to deploy clones of | ### l2StateSender diff --git a/docs/child/RootMintableERC721Predicate.md b/docs/child/RootMintableERC721Predicate.md index f85d2910..037dce08 100644 --- a/docs/child/RootMintableERC721Predicate.md +++ b/docs/child/RootMintableERC721Predicate.md @@ -349,8 +349,8 @@ Initilization function for RootMintableERC721Predicate | Name | Type | Description | |---|---|---| -| newL2StateSender | address | Address of StateSender to send deposit information to | -| newStateReceiver | address | Address of ExitHelper to receive withdrawal information from | +| newL2StateSender | address | Address of L2StateSender to send deposit information to | +| newStateReceiver | address | Address of StateReceiver to receive withdrawal information from | | newChildERC721Predicate | address | Address of child ERC721 predicate to communicate with | | newChildTokenTemplate | address | Address of child token template to calculate child token addresses | diff --git a/docs/lib/ChildManagerLib.md b/docs/lib/ChildManagerLib.md deleted file mode 100644 index 2c9b6934..00000000 --- a/docs/lib/ChildManagerLib.md +++ /dev/null @@ -1,12 +0,0 @@ -# ChildManagerLib - - - - - - - - - - - diff --git a/docs/lib/StakeManagerLib.md b/docs/lib/StakeManagerLib.md deleted file mode 100644 index 3e72d4c1..00000000 --- a/docs/lib/StakeManagerLib.md +++ /dev/null @@ -1,12 +0,0 @@ -# StakeManagerLib - - - - - - - - - - - diff --git a/docs/root/ChildMintableERC1155Predicate.md b/docs/root/ChildMintableERC1155Predicate.md index 4eacef7f..5f244e1e 100644 --- a/docs/root/ChildMintableERC1155Predicate.md +++ b/docs/root/ChildMintableERC1155Predicate.md @@ -135,7 +135,7 @@ function exitHelper() external view returns (address) function initialize(address newStateSender, address newExitHelper, address newRootERC1155Predicate, address newChildTokenTemplate) external nonpayable ``` -Initilization function for ChildERC1155Predicate +Initilization function for ChildMintableERC1155Predicate *Can only be called once.* @@ -143,8 +143,8 @@ Initilization function for ChildERC1155Predicate | Name | Type | Description | |---|---|---| -| newStateSender | address | Address of L2StateSender to send exit information to | -| newExitHelper | address | Address of StateReceiver to receive deposit information from | +| newStateSender | address | Address of StateSender to send exit information to | +| newExitHelper | address | Address of ExitHelper to receive deposit information from | | newRootERC1155Predicate | address | Address of root ERC1155 predicate to communicate with | | newChildTokenTemplate | address | Address of child token implementation to deploy clones of | diff --git a/docs/root/ChildMintableERC20Predicate.md b/docs/root/ChildMintableERC20Predicate.md index 536c5f2d..c3859dd1 100644 --- a/docs/root/ChildMintableERC20Predicate.md +++ b/docs/root/ChildMintableERC20Predicate.md @@ -101,7 +101,7 @@ function exitHelper() external view returns (address) function initialize(address newStateSender, address newExitHelper, address newRootERC20Predicate, address newChildTokenTemplate) external nonpayable ``` -Initilization function for RootERC20Predicate +Initilization function for ChildMintableERC20Predicate *Can only be called once.* @@ -111,7 +111,7 @@ Initilization function for RootERC20Predicate |---|---|---| | newStateSender | address | Address of StateSender to send deposit information to | | newExitHelper | address | Address of ExitHelper to receive withdrawal information from | -| newRootERC20Predicate | address | Address of child ERC20 predicate to communicate with | +| newRootERC20Predicate | address | Address of root ERC20 predicate to communicate with | | newChildTokenTemplate | address | undefined | ### onL2StateReceive diff --git a/docs/root/ChildMintableERC721Predicate.md b/docs/root/ChildMintableERC721Predicate.md index b60b305b..68987bb1 100644 --- a/docs/root/ChildMintableERC721Predicate.md +++ b/docs/root/ChildMintableERC721Predicate.md @@ -135,7 +135,7 @@ function exitHelper() external view returns (address) function initialize(address newStateSender, address newExitHelper, address newRootERC721Predicate, address newChildTokenTemplate) external nonpayable ``` -Initilization function for ChildERC721Predicate +Initilization function for ChildMintableERC721Predicate *Can only be called once. `newNativeTokenRootAddress` should be set to zero where root token does not exist.* @@ -144,7 +144,7 @@ Initilization function for ChildERC721Predicate | Name | Type | Description | |---|---|---| | newStateSender | address | Address of StateSender to send exit information to | -| newExitHelper | address | Address of StateReceiver to receive deposit information from | +| newExitHelper | address | Address of ExitHelper to receive deposit information from | | newRootERC721Predicate | address | Address of root ERC721 predicate to communicate with | | newChildTokenTemplate | address | Address of child token implementation to deploy clones of | diff --git a/docs/root/staking/StakeManagerChildData.md b/docs/root/staking/StakeManagerChildData.md new file mode 100644 index 00000000..b36e345b --- /dev/null +++ b/docs/root/staking/StakeManagerChildData.md @@ -0,0 +1,12 @@ +# StakeManagerChildData + + + +> StakeManagerChildData + +Holds all look-up between child chain manager contract and child chain id. Note that this is contract is designed to be included in StakeManager. It is upgradeable. + + + + + diff --git a/docs/root/staking/StakeManagerStakingData.md b/docs/root/staking/StakeManagerStakingData.md new file mode 100644 index 00000000..cbf2e4b3 --- /dev/null +++ b/docs/root/staking/StakeManagerStakingData.md @@ -0,0 +1,12 @@ +# StakeManagerStakingData + + + +> StakeManagerStakingData + +Holds all staking related data. Note that this is contract is designed to be included in StakeManager. It is upgradeable. + + + + + diff --git a/package-lock.json b/package-lock.json index 038aafb9..0c26a93e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@openzeppelin/contracts": "^4.8.3", "@openzeppelin/contracts-upgradeable": "^4.8.3", "@primitivefi/hardhat-dodoc": "^0.2.3", - "hardhat": "^2.14.0", + "hardhat": "^2.16.0", "keccak256": "^1.0.6", "mcl-wasm": "^1.0.2", "merkletreejs": "^0.3.10" @@ -2983,6 +2983,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -5558,7 +5559,8 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -5614,6 +5616,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -5879,9 +5882,9 @@ } }, "node_modules/hardhat": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.0.tgz", - "integrity": "sha512-73jsInY4zZahMSVFurSK+5TNCJTXMv+vemvGia0Ac34Mm19fYp6vEPVGF3sucbumszsYxiTT2TbS8Ii2dsDSoQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.16.0.tgz", + "integrity": "sha512-7VQEJPQRAZdtrYUZaU9GgCpP3MBNy/pTdscARNJQMWKj5C+R7V32G5uIZKIqZ4QiqXa6CBfxxe+G+ahxUbHZHA==", "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", @@ -5922,7 +5925,6 @@ "mnemonist": "^0.38.0", "mocha": "^10.0.0", "p-map": "^4.0.0", - "qs": "^6.7.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", @@ -6399,6 +6401,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -6451,6 +6454,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -7985,6 +7989,7 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8420,6 +8425,8 @@ "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "peer": true, "dependencies": { "side-channel": "^1.0.4" }, @@ -9144,6 +9151,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -13427,6 +13435,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -15440,7 +15449,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "function.prototype.name": { "version": "1.1.5", @@ -15481,6 +15491,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -15675,9 +15686,9 @@ } }, "hardhat": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.0.tgz", - "integrity": "sha512-73jsInY4zZahMSVFurSK+5TNCJTXMv+vemvGia0Ac34Mm19fYp6vEPVGF3sucbumszsYxiTT2TbS8Ii2dsDSoQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.16.0.tgz", + "integrity": "sha512-7VQEJPQRAZdtrYUZaU9GgCpP3MBNy/pTdscARNJQMWKj5C+R7V32G5uIZKIqZ4QiqXa6CBfxxe+G+ahxUbHZHA==", "requires": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", @@ -15718,7 +15729,6 @@ "mnemonist": "^0.38.0", "mocha": "^10.0.0", "p-map": "^4.0.0", - "qs": "^6.7.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", @@ -16155,6 +16165,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -16188,7 +16199,8 @@ "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true }, "has-tostringtag": { "version": "1.0.0", @@ -17326,7 +17338,8 @@ "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true }, "object-keys": { "version": "1.1.1", @@ -17652,6 +17665,8 @@ "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "peer": true, "requires": { "side-channel": "^1.0.4" } @@ -18174,6 +18189,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", diff --git a/package.json b/package.json index 6eb4f97b..52370ac1 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@openzeppelin/contracts": "^4.8.3", "@openzeppelin/contracts-upgradeable": "^4.8.3", "@primitivefi/hardhat-dodoc": "^0.2.3", - "hardhat": "^2.14.0", + "hardhat": "^2.16.0", "keccak256": "^1.0.6", "mcl-wasm": "^1.0.2", "merkletreejs": "^0.3.10" From 80a14cee8bd269599977dd081098a289d6998b23 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Wed, 5 Jul 2023 08:42:56 +1000 Subject: [PATCH 3/6] Addressed pull request feedback --- contracts/root/staking/StakeManager.sol | 10 +++++----- contracts/root/staking/StakeManagerChildData.sol | 12 +++++++----- contracts/root/staking/StakeManagerStakingData.sol | 9 ++++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/contracts/root/staking/StakeManager.sol b/contracts/root/staking/StakeManager.sol index 1d3cb0cf..8fc1ac5e 100644 --- a/contracts/root/staking/StakeManager.sol +++ b/contracts/root/staking/StakeManager.sol @@ -11,10 +11,10 @@ import "./StakeManagerStakingData.sol"; contract StakeManager is IStakeManager, Initializable, StakeManagerChildData, StakeManagerStakingData { using SafeERC20 for IERC20; - IERC20 internal matic; + IERC20 internal stakingToken; - function initialize(address newMatic) public initializer { - matic = IERC20(newMatic); + function initialize(address newStakingToken) public initializer { + stakingToken = IERC20(newStakingToken); } /** @@ -33,7 +33,7 @@ contract StakeManager is IStakeManager, Initializable, StakeManagerChildData, St function stakeFor(uint256 id, uint256 amount) external { require(id != 0 && id <= counter, "INVALID_ID"); // slither-disable-next-line reentrancy-benign,reentrancy-events - matic.safeTransferFrom(msg.sender, address(this), amount); + stakingToken.safeTransferFrom(msg.sender, address(this), amount); // calling the library directly once fixes the coverage issue // https://github.com/foundry-rs/foundry/issues/4854#issuecomment-1528897219 _addStake(msg.sender, id, amount); @@ -125,7 +125,7 @@ contract StakeManager is IStakeManager, Initializable, StakeManagerChildData, St function _withdrawStake(address validator, address to, uint256 amount) private { _withdrawStake(validator, amount); // slither-disable-next-line reentrancy-events - matic.safeTransfer(to, amount); + stakingToken.safeTransfer(to, amount); emit StakeWithdrawn(validator, to, amount); } } diff --git a/contracts/root/staking/StakeManagerChildData.sol b/contracts/root/staking/StakeManagerChildData.sol index e7d914cf..157591a4 100644 --- a/contracts/root/staking/StakeManagerChildData.sol +++ b/contracts/root/staking/StakeManagerChildData.sol @@ -14,10 +14,6 @@ abstract contract StakeManagerChildData { // child chain manager contract address to child chain id. mapping(address => uint256) private ids; - // Storage gap - // solhint-disable-next-line var-name-mixedcase - uint256[1000] private __StorageGapStakeManagerChildData; - /** * @notice Register a child chain manager contract and allocate a child chain id. * @param manager Child chain manager contract address. @@ -25,7 +21,9 @@ abstract contract StakeManagerChildData { */ function _registerChild(address manager) internal returns (uint256 id) { assert(manager != address(0)); - id = ++counter; + unchecked { + id = ++counter; + } managers[id] = manager; ids[manager] = id; } @@ -49,4 +47,8 @@ abstract contract StakeManagerChildData { id = ids[manager]; require(id != 0, "Invalid manager"); } + + // Storage gap + // solhint-disable-next-line var-name-mixedcase + uint256[50] private __StorageGapStakeManagerChildData; } diff --git a/contracts/root/staking/StakeManagerStakingData.sol b/contracts/root/staking/StakeManagerStakingData.sol index a080d5ab..54b18ec6 100644 --- a/contracts/root/staking/StakeManagerStakingData.sol +++ b/contracts/root/staking/StakeManagerStakingData.sol @@ -18,11 +18,6 @@ abstract contract StakeManagerStakingData { // validator address => withdrawable stake. mapping(address => uint256) private withdrawableStakes; - // Storage gap - // solhint-disable-next-line var-name-mixedcase - uint256[1000] private __StorageGapStakeManagerStakingData; - - function _addStake(address validator, uint256 id, uint256 amount) internal { stakes[validator][id] += amount; totalStakePerChild[id] += amount; @@ -57,4 +52,8 @@ abstract contract StakeManagerStakingData { function _totalStakeOf(address validator) internal view returns (uint256 amount) { amount = totalStakes[validator]; } + + // Storage gap + // solhint-disable-next-line var-name-mixedcase + uint256[50] private __StorageGapStakeManagerStakingData; } From 762c249f1c02ee6125033b4d4c5ddb7e82400b82 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Thu, 6 Jul 2023 15:24:47 +1000 Subject: [PATCH 4/6] Change storage gap variable names to __gap and private storage variables now start with an underscore. --- contracts/root/staking/StakeManager.sol | 2 +- .../root/staking/StakeManagerChildData.sol | 14 +++---- .../root/staking/StakeManagerStakingData.sol | 40 +++++++++---------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/contracts/root/staking/StakeManager.sol b/contracts/root/staking/StakeManager.sol index 8fc1ac5e..7d43780a 100644 --- a/contracts/root/staking/StakeManager.sol +++ b/contracts/root/staking/StakeManager.sol @@ -84,7 +84,7 @@ contract StakeManager is IStakeManager, Initializable, StakeManagerChildData, St * @inheritdoc IStakeManager */ function totalStake() external view returns (uint256 amount) { - amount = theTotalStake; + amount = _totalStake; } /** diff --git a/contracts/root/staking/StakeManagerChildData.sol b/contracts/root/staking/StakeManagerChildData.sol index 157591a4..db558f99 100644 --- a/contracts/root/staking/StakeManagerChildData.sol +++ b/contracts/root/staking/StakeManagerChildData.sol @@ -10,9 +10,9 @@ abstract contract StakeManagerChildData { // Highest child chain id allocated thus far. Child chain id 0x00 is an invalid id. uint256 internal counter; // child chain id to child chain manager contract address. - mapping(uint256 => address) private managers; + mapping(uint256 => address) private _managers; // child chain manager contract address to child chain id. - mapping(address => uint256) private ids; + mapping(address => uint256) private _ids; /** * @notice Register a child chain manager contract and allocate a child chain id. @@ -24,8 +24,8 @@ abstract contract StakeManagerChildData { unchecked { id = ++counter; } - managers[id] = manager; - ids[manager] = id; + _managers[id] = manager; + _ids[manager] = id; } /** @@ -34,7 +34,7 @@ abstract contract StakeManagerChildData { * @return manager Child chain manager contract address. */ function _managerOf(uint256 id) internal view returns (address manager) { - manager = managers[id]; + manager = _managers[id]; require(manager != address(0), "Invalid id"); } @@ -44,11 +44,11 @@ abstract contract StakeManagerChildData { * @return id Child chain id. */ function _idFor(address manager) internal view returns (uint256 id) { - id = ids[manager]; + id = _ids[manager]; require(id != 0, "Invalid manager"); } // Storage gap // solhint-disable-next-line var-name-mixedcase - uint256[50] private __StorageGapStakeManagerChildData; + uint256[50] private __gap; } diff --git a/contracts/root/staking/StakeManagerStakingData.sol b/contracts/root/staking/StakeManagerStakingData.sol index 54b18ec6..9c2226a1 100644 --- a/contracts/root/staking/StakeManagerStakingData.sol +++ b/contracts/root/staking/StakeManagerStakingData.sol @@ -8,52 +8,52 @@ pragma solidity 0.8.19; * Note that this is contract is designed to be included in StakeManager. It is upgradeable. */ abstract contract StakeManagerStakingData { - uint256 internal theTotalStake; + uint256 internal _totalStake; // validator => child => amount - mapping(address => mapping(uint256 => uint256)) private stakes; + mapping(address => mapping(uint256 => uint256)) private _stakes; // child chain id => total stake - mapping(uint256 => uint256) private totalStakePerChild; + mapping(uint256 => uint256) private _totalStakePerChild; // validator address => stake across all child chains. - mapping(address => uint256) private totalStakes; + mapping(address => uint256) private _totalStakes; // validator address => withdrawable stake. - mapping(address => uint256) private withdrawableStakes; + mapping(address => uint256) private _withdrawableStakes; function _addStake(address validator, uint256 id, uint256 amount) internal { - stakes[validator][id] += amount; - totalStakePerChild[id] += amount; - totalStakes[validator] += amount; - theTotalStake += amount; + _stakes[validator][id] += amount; + _totalStakePerChild[id] += amount; + _totalStakes[validator] += amount; + _totalStake += amount; } function _removeStake(address validator, uint256 id, uint256 amount) internal { - stakes[validator][id] -= amount; - totalStakePerChild[id] -= amount; - totalStakes[validator] -= amount; - theTotalStake -= amount; - withdrawableStakes[validator] += amount; + _stakes[validator][id] -= amount; + _totalStakePerChild[id] -= amount; + _totalStakes[validator] -= amount; + _totalStake -= amount; + _withdrawableStakes[validator] += amount; } function _withdrawStake(address validator, uint256 amount) internal { - withdrawableStakes[validator] -= amount; + _withdrawableStakes[validator] -= amount; } function _withdrawableStakeOf(address validator) internal view returns (uint256 amount) { - amount = withdrawableStakes[validator]; + amount = _withdrawableStakes[validator]; } function _totalStakeOfChild(uint256 id) internal view returns (uint256 amount) { - amount = totalStakePerChild[id]; + amount = _totalStakePerChild[id]; } function _stakeOf(address validator, uint256 id) internal view returns (uint256 amount) { - amount = stakes[validator][id]; + amount = _stakes[validator][id]; } function _totalStakeOf(address validator) internal view returns (uint256 amount) { - amount = totalStakes[validator]; + amount = _totalStakes[validator]; } // Storage gap // solhint-disable-next-line var-name-mixedcase - uint256[50] private __StorageGapStakeManagerStakingData; + uint256[50] private __gap; } From 3c3b12112cfe93297e45d7e84cf501ad46006381 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Tue, 18 Jul 2023 08:27:37 +1000 Subject: [PATCH 5/6] Fixed prettier issues --- contracts/root/staking/StakeManagerChildData.sol | 6 +++--- contracts/root/staking/StakeManagerStakingData.sol | 3 +-- .../contracts-upgradeable/governance/GovernorUpgradeable.md | 2 +- .../extensions/GovernorTimelockControlUpgradeable.md | 2 +- .../extensions/GovernorVotesQuorumFractionUpgradeable.md | 2 +- .../governance/extensions/GovernorVotesUpgradeable.md | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/contracts/root/staking/StakeManagerChildData.sol b/contracts/root/staking/StakeManagerChildData.sol index f819713b..b87a9d7d 100644 --- a/contracts/root/staking/StakeManagerChildData.sol +++ b/contracts/root/staking/StakeManagerChildData.sol @@ -28,7 +28,7 @@ abstract contract StakeManagerChildData { _ids[manager] = id; } - /** + /** * @notice Get the child chain manager contract that corresponds to a child chain id. * @param id Child chain id. * @return manager Child chain manager contract address. @@ -38,7 +38,7 @@ abstract contract StakeManagerChildData { require(manager != address(0), "StakeManagerChildData: INVALID_ID"); } - /** + /** * @notice Get the child chain id that corresponds to a child chain manager contract. * @param manager Child chain manager contract address. * @return id Child chain id. @@ -48,7 +48,7 @@ abstract contract StakeManagerChildData { require(id != 0, "StakeManagerChildData: INVALID_MANAGER"); } - // Storage gap + // Storage gap // solhint-disable-next-line var-name-mixedcase uint256[50] private __gap; } diff --git a/contracts/root/staking/StakeManagerStakingData.sol b/contracts/root/staking/StakeManagerStakingData.sol index 9c2226a1..58ef95e2 100644 --- a/contracts/root/staking/StakeManagerStakingData.sol +++ b/contracts/root/staking/StakeManagerStakingData.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; - /** * @title StakeManagerStakingData * @notice Holds all staking related data. @@ -53,7 +52,7 @@ abstract contract StakeManagerStakingData { amount = _totalStakes[validator]; } - // Storage gap + // Storage gap // solhint-disable-next-line var-name-mixedcase uint256[50] private __gap; } diff --git a/docs/elin/contracts-upgradeable/governance/GovernorUpgradeable.md b/docs/elin/contracts-upgradeable/governance/GovernorUpgradeable.md index 348ae907..1db01533 100644 --- a/docs/elin/contracts-upgradeable/governance/GovernorUpgradeable.md +++ b/docs/elin/contracts-upgradeable/governance/GovernorUpgradeable.md @@ -574,7 +574,7 @@ function propose(address[] targets, uint256[] values, bytes[] calldatas, string -*See {IGovernor-propose}.* +*See {IGovernor-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}.* #### Parameters diff --git a/docs/elin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.md b/docs/elin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.md index b7df2a4a..45121f1a 100644 --- a/docs/elin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.md +++ b/docs/elin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.md @@ -596,7 +596,7 @@ function propose(address[] targets, uint256[] values, bytes[] calldatas, string -*See {IGovernor-propose}.* +*See {IGovernor-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}.* #### Parameters diff --git a/docs/elin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.md b/docs/elin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.md index f9f38ada..5f24b223 100644 --- a/docs/elin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.md +++ b/docs/elin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.md @@ -574,7 +574,7 @@ function propose(address[] targets, uint256[] values, bytes[] calldatas, string -*See {IGovernor-propose}.* +*See {IGovernor-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}.* #### Parameters diff --git a/docs/elin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.md b/docs/elin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.md index 0f54f4ac..b52e384f 100644 --- a/docs/elin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.md +++ b/docs/elin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.md @@ -574,7 +574,7 @@ function propose(address[] targets, uint256[] values, bytes[] calldatas, string -*See {IGovernor-propose}.* +*See {IGovernor-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}.* #### Parameters From 13105ea06eeb5731b030c18a297d68b3b5ae3fb5 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Thu, 3 Aug 2023 08:52:47 +1000 Subject: [PATCH 6/6] Fix slither issues --- contracts/root/staking/StakeManagerChildData.sol | 3 ++- contracts/root/staking/StakeManagerStakingData.sol | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/root/staking/StakeManagerChildData.sol b/contracts/root/staking/StakeManagerChildData.sol index b87a9d7d..185ae8c4 100644 --- a/contracts/root/staking/StakeManagerChildData.sol +++ b/contracts/root/staking/StakeManagerChildData.sol @@ -12,6 +12,7 @@ abstract contract StakeManagerChildData { // child chain id to child chain manager contract address. mapping(uint256 => address) private _managers; // child chain manager contract address to child chain id. + // slither-disable-next-line naming-convention mapping(address => uint256) internal _ids; /** @@ -49,6 +50,6 @@ abstract contract StakeManagerChildData { } // Storage gap - // solhint-disable-next-line var-name-mixedcase + // slither-disable-next-line unused-state,naming-convention uint256[50] private __gap; } diff --git a/contracts/root/staking/StakeManagerStakingData.sol b/contracts/root/staking/StakeManagerStakingData.sol index 58ef95e2..9d1b9042 100644 --- a/contracts/root/staking/StakeManagerStakingData.sol +++ b/contracts/root/staking/StakeManagerStakingData.sol @@ -7,6 +7,7 @@ pragma solidity 0.8.19; * Note that this is contract is designed to be included in StakeManager. It is upgradeable. */ abstract contract StakeManagerStakingData { + // slither-disable-next-line naming-convention uint256 internal _totalStake; // validator => child => amount mapping(address => mapping(uint256 => uint256)) private _stakes; @@ -53,6 +54,6 @@ abstract contract StakeManagerStakingData { } // Storage gap - // solhint-disable-next-line var-name-mixedcase + // slither-disable-next-line unused-state,naming-convention uint256[50] private __gap; }