From 12ce4afe35cf4d75664615e80effb8208b41c86c Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 15:44:18 +0530 Subject: [PATCH 01/16] new: batch deposit of ERC721 tokens in mintable predicate --- .../MintableERC721Predicate.sol | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/contracts/root/TokenPredicates/MintableERC721Predicate.sol b/contracts/root/TokenPredicates/MintableERC721Predicate.sol index f373da73..b5c49af0 100644 --- a/contracts/root/TokenPredicates/MintableERC721Predicate.sol +++ b/contracts/root/TokenPredicates/MintableERC721Predicate.sol @@ -27,6 +27,13 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial uint256 tokenId ); + event LockedMintableERC721Batch( + address indexed depositor, + address indexed depositReceiver, + address indexed rootToken, + uint256[] tokenIds + ); + constructor() public {} function initialize(address _owner) external initializer { @@ -52,11 +59,11 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial } /** - * @notice Lock ERC721 tokens for deposit, callable only by manager + * @notice Lock ERC721 token(s) for deposit, callable only by manager * @param depositor Address who wants to deposit token * @param depositReceiver Address (address) who wants to receive token on child chain * @param rootToken Token which gets deposited - * @param depositData ABI encoded tokenId + * @param depositData ABI encoded tokenId(s). It's possible to deposit batch of tokens. */ function lockTokens( address depositor, @@ -68,9 +75,43 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial override only(MANAGER_ROLE) { - uint256 tokenId = abi.decode(depositData, (uint256)); - emit LockedMintableERC721(depositor, depositReceiver, rootToken, tokenId); - IMintableERC721(rootToken).safeTransferFrom(depositor, address(this), tokenId); + + // Locking single ERC721 token + if (depositData.length == 32) { + + uint256 tokenId = abi.decode(depositData, (uint256)); + + // Emitting event that single token is getting locked in predicate + emit LockedMintableERC721(depositor, depositReceiver, rootToken, tokenId); + + // Transferring token to this address, which will be + // released when attempted to be unlocked + IERC721(rootToken).safeTransferFrom(depositor, address(this), tokenId); + + } else { + // Locking a set a ERC721 token(s) + + uint256[] memory tokenIds = abi.decode(depositData, (uint256[])); + + // Emitting event that a set of ERC721 tokens are getting lockec + // in this predicate contract + emit LockedMintableERC721Batch(depositor, depositReceiver, rootToken, tokenIds); + + // These many tokens are attempted to be deposited + // by user + uint256 length = tokenIds.length; + require(length <= BATCH_LIMIT, "MintableERC721Predicate: EXCEEDS_BATCH_LIMIT"); + + // Iteratively trying to transfer ERC721 token + // to this predicate address + for (uint256 i; i < length; i++) { + + IERC721(rootToken).safeTransferFrom(depositor, address(this), tokenIds[i]); + + } + + } + } /** From 7887b7bbffd0197a3c1871d7ccd65d351312cafa Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 16:12:20 +0530 Subject: [PATCH 02/16] new: batch withdraw in mintable ERC721 predicate --- .../MintableERC721Predicate.sol | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/contracts/root/TokenPredicates/MintableERC721Predicate.sol b/contracts/root/TokenPredicates/MintableERC721Predicate.sol index b5c49af0..21cf5ab3 100644 --- a/contracts/root/TokenPredicates/MintableERC721Predicate.sol +++ b/contracts/root/TokenPredicates/MintableERC721Predicate.sol @@ -15,9 +15,11 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial bytes32 public constant MANAGER_ROLE = 0x241ecf16d79d0f8dbfb92cbc07fe17840425976cf0667f022fe9877caa831b08; // keccak256("MintableERC721") bytes32 public constant TOKEN_TYPE = 0xd4392723c111fcb98b073fe55873efb447bcd23cd3e49ec9ea2581930cd01ddc; - // keccak("Transfer(address,address,uint256)") + // keccak256("Transfer(address,address,uint256)") bytes32 public constant TRANSFER_EVENT_SIG = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; - // keccak("TransferWithMetadata(address,address,uint256,string)") + // keccak256("WithdrawnBatch(address,uint256[])") + bytes32 public constant WITHDRAW_BATCH_EVENT_SIG = 0xf871896b17e9cb7a64941c62c188a4f5c621b86800e3d15452ece01ce56073df; + // keccak256("TransferWithMetadata(address,address,uint256,string)") bytes32 public constant TRANSFER_WITH_METADATA_EVENT_SIG = 0xf3c6803764de9a0fc1c2acb6a71f53407c5e2b9a3e04973e6586da23d64ecaa5; event LockedMintableERC721( @@ -158,6 +160,28 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial token.mint(withdrawer, tokenId); } + } else if (bytes32(logTopicRLPList[0].toUint()) == WITHDRAW_BATCH_EVENT_SIG) { // topic0 is event sig + // If it's a simple batch exit, where a set of + // ERC721s were burnt in child chain with event signature + // looking like `WithdrawnBatch(address indexed user, uint256[] tokenIds);` + // + // @note This doesn't allow transfer of metadata cross chain + // For that check below `else if` block + + address withdrawer = address(logTopicRLPList[1].toUint()); // topic1 is from address + + // RLP encoded tokenId list + bytes memory logData = logRLPList[2].toBytes(); + + (uint256[] memory tokenIds) = abi.decode(logData, (uint256[])); + uint256 length = tokenIds.length; + + for (uint256 i; i < length; i++) { + + IERC721(rootToken).safeTransferFrom(address(this), withdrawer, tokenIds[i]); + + } + } else if (bytes32(logTopicRLPList[0].toUint()) == TRANSFER_WITH_METADATA_EVENT_SIG) { // If this is NFT exit with metadata i.e. URI 👆 // From 3b79ef6060e6bf03b135e9de5c11817b5c2da81f Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 16:21:32 +0530 Subject: [PATCH 03/16] new: batch deposit/ withdraw in child mintable ERC721 implementation --- .../child/ChildToken/ChildMintableERC721.sol | 61 +++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/contracts/child/ChildToken/ChildMintableERC721.sol b/contracts/child/ChildToken/ChildMintableERC721.sol index 1572fec8..a5f4e742 100644 --- a/contracts/child/ChildToken/ChildMintableERC721.sol +++ b/contracts/child/ChildToken/ChildMintableERC721.sol @@ -19,6 +19,14 @@ contract ChildMintableERC721 is event TransferWithMetadata(address indexed from, address indexed to, uint256 indexed tokenId, bytes metaData); + // limit batching of tokens due to gas limit restrictions + uint256 public constant BATCH_LIMIT = 20; + + event WithdrawnBatch( + address indexed user, + uint256[] tokenIds + ); + constructor( string memory name_, string memory symbol_, @@ -44,20 +52,34 @@ contract ChildMintableERC721 is /** * @notice called when token is deposited on root chain * @dev Should be callable only by ChildChainManager - * Should handle deposit by minting the required tokenId for user + * Should handle deposit by minting the required tokenId(s) for user * Should set `withdrawnTokens` mapping to `false` for the tokenId being deposited * Minting can also be done by other functions * @param user user address for whom deposit is being done - * @param depositData abi encoded tokenId + * @param depositData abi encoded tokenIds. Batch deposit also supported. */ function deposit(address user, bytes calldata depositData) external override only(DEPOSITOR_ROLE) { - uint256 tokenId = abi.decode(depositData, (uint256)); - withdrawnTokens[tokenId] = false; - _mint(user, tokenId); + + // deposit single + if (depositData.length == 32) { + uint256 tokenId = abi.decode(depositData, (uint256)); + withdrawnTokens[tokenId] = false; + _mint(user, tokenId); + + // deposit batch + } else { + uint256[] memory tokenIds = abi.decode(depositData, (uint256[])); + uint256 length = tokenIds.length; + for (uint256 i; i < length; i++) { + withdrawnTokens[tokenIds[i]] = false; + _mint(user, tokenIds[i]); + } + } + } /** @@ -73,6 +95,35 @@ contract ChildMintableERC721 is _burn(tokenId); } + /** + * @notice called when user wants to withdraw multiple tokens back to root chain + * @dev Should burn user's tokens. This transaction will be verified when exiting on root chain + * @param tokenIds tokenId list to withdraw + */ + function withdrawBatch(uint256[] calldata tokenIds) external { + + uint256 length = tokenIds.length; + require(length <= BATCH_LIMIT, "ChildMintableERC721: EXCEEDS_BATCH_LIMIT"); + + // Iteratively burn ERC721 tokens, for performing + // batch withdraw + for (uint256 i; i < length; i++) { + + uint256 tokenId = tokenIds[i]; + + require(_msgSender() == ownerOf(tokenId), string(abi.encodePacked("ChildMintableERC721: INVALID_TOKEN_OWNER ", tokenId))); + withdrawnTokens[tokenId] = true; + _burn(tokenId); + + } + + // At last emit this event, which will be used + // in MintableERC721 predicate contract on L1 + // while verifying burn proof + emit WithdrawnBatch(_msgSender(), tokenIds); + + } + /** * @notice called when user wants to withdraw token back to root chain with token URI * @dev Should handle withraw by burning user's token. From dff39a0b513bbb15efa6a2e0922a49f093dfe487 Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 16:46:17 +0530 Subject: [PATCH 04/16] new: withdraw a batch of tokens with respective metadata, for child token --- .../child/ChildToken/ChildMintableERC721.sol | 58 ++++++++++++++++--- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/contracts/child/ChildToken/ChildMintableERC721.sol b/contracts/child/ChildToken/ChildMintableERC721.sol index a5f4e742..3edf92a8 100644 --- a/contracts/child/ChildToken/ChildMintableERC721.sol +++ b/contracts/child/ChildToken/ChildMintableERC721.sol @@ -17,15 +17,12 @@ contract ChildMintableERC721 is bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); mapping (uint256 => bool) public withdrawnTokens; - event TransferWithMetadata(address indexed from, address indexed to, uint256 indexed tokenId, bytes metaData); - // limit batching of tokens due to gas limit restrictions uint256 public constant BATCH_LIMIT = 20; - event WithdrawnBatch( - address indexed user, - uint256[] tokenIds - ); + event WithdrawnBatch(address indexed user, uint256[] tokenIds); + event TransferWithMetadata(address indexed from, address indexed to, uint256 indexed tokenId, bytes metaData); + event WithdrawBatchWithMetadata(address indexed from, address indexed to, uint256[] tokenIds, bytes[] metaData); constructor( string memory name_, @@ -130,9 +127,6 @@ contract ChildMintableERC721 is * Should set `withdrawnTokens` mapping to `true` for the tokenId being withdrawn * This transaction will be verified when exiting on root chain * - * Before calling this function, you may want calling `encodeTokenMetadata` - * and get metadata to be transferred from L2 to L1 during exit - * * @param tokenId tokenId to withdraw */ function withdrawWithMetadata(uint256 tokenId) external { @@ -147,6 +141,52 @@ contract ChildMintableERC721 is } + /** + * @notice called when user wants to withdraw tokens i.e. a batch of tokens, back to root chain with all respective metaData + * @dev Should handle withraw by burning user's token. + * Should set `withdrawnTokens` mapping to `true` for the tokenId being withdrawn + * This transaction will be verified when exiting on root chain + * + * @param tokenId tokenId to withdraw + */ + function withdrawBatchWithMetadata(uint256 calldata tokenIds) external { + + uint256 length = tokenIds.length; + require(length <= BATCH_LIMIT, "ChildMintableERC721: EXCEEDS_BATCH_LIMIT"); + + // During iteratively burning tokens, attempting + // to store respective metadata + // + // To be emitted with event, which will be + // verified on L1 & used for setting token metadata + // when exiting from L2 to L1 + uint256[length] memory metaData; + + // Iteratively burn ERC721 tokens, for performing + // batch withdraw + for (uint256 i; i < length; i++) { + + uint256 tokenId = tokenIds[i]; + + require(_msgSender() == ownerOf(tokenId), string(abi.encodePacked("ChildMintableERC721: INVALID_TOKEN_OWNER ", tokenId))); + // Marking token has been withdrawn + withdrawnTokens[tokenId] = true; + + // Storing metadata of respective token + // in ordered fashion + metaData[i] = this.encodeTokenMetadata(tokenId); + // Then burning token + _burn(tokenId); + + } + + // At last emit this event, which will be used + // in MintableERC721 predicate contract on L1 + // while verifying burn proof + emit WithdrawBatchWithMetadata(_msgSender(), address(0), tokenIds, metaData); + + } + /** * @notice This method is supposed to be called by client when withdrawing token with metadata * and pass return value of this function as second paramter of `withdrawWithMetadata` method From 824caf0b526e4324d48ae593df1e71b38539ff34 Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 18:33:27 +0530 Subject: [PATCH 05/16] new: batch withdraw op for mintable ERC721 --- .../MintableERC721Predicate.sol | 76 ++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/contracts/root/TokenPredicates/MintableERC721Predicate.sol b/contracts/root/TokenPredicates/MintableERC721Predicate.sol index 21cf5ab3..46514743 100644 --- a/contracts/root/TokenPredicates/MintableERC721Predicate.sol +++ b/contracts/root/TokenPredicates/MintableERC721Predicate.sol @@ -19,8 +19,10 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial bytes32 public constant TRANSFER_EVENT_SIG = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; // keccak256("WithdrawnBatch(address,uint256[])") bytes32 public constant WITHDRAW_BATCH_EVENT_SIG = 0xf871896b17e9cb7a64941c62c188a4f5c621b86800e3d15452ece01ce56073df; - // keccak256("TransferWithMetadata(address,address,uint256,string)") - bytes32 public constant TRANSFER_WITH_METADATA_EVENT_SIG = 0xf3c6803764de9a0fc1c2acb6a71f53407c5e2b9a3e04973e6586da23d64ecaa5; + // keccak256("TransferWithMetadata(address,address,uint256,bytes)") + bytes32 public constant TRANSFER_WITH_METADATA_EVENT_SIG = 0xf94915c6d1fd521cee85359239227480c7e8776d7caf1fc3bacad5c269b66a14; + // keccak256("WithdrawBatchWithMetadata(address,address,uint256[],bytes[])") + bytes32 public constant WITHDRAW_BATCH_WITH_METADATA_EVENT_SIG = 0x4f83879b5380fb2113c27cb03563cd911fe5f79f11b4ce5c470b40070c2acd86; event LockedMintableERC721( address indexed depositor, @@ -176,9 +178,27 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial (uint256[] memory tokenIds) = abi.decode(logData, (uint256[])); uint256 length = tokenIds.length; + IMintableERC721 token = IMintableERC721(rootToken); + for (uint256 i; i < length; i++) { - IERC721(rootToken).safeTransferFrom(address(this), withdrawer, tokenIds[i]); + uint256 tokenId = tokenIds[i]; + + // Check if token exists or not + // + // If does, transfer token to withdrawer + if (token.exists(tokenId)) { + token.safeTransferFrom( + address(this), + withdrawer, + tokenId + ); + } else { + // If token was minted on L2 + // we'll mint it here, on L1, during + // exiting from L2 + token.mint(withdrawer, tokenId); + } } @@ -217,6 +237,56 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial token.mint(withdrawer, tokenId, logRLPList[2].toBytes()); } + } else if (bytes32(logTopicRLPList[0].toUint()) == WITHDRAW_BATCH_WITH_METADATA_EVENT_SIG) { + // If this is batch NFT exit with all respective metadata 👆 + // + // Note: This is nothing but batch version of previous + // block of code + + address withdrawer = address(logTopicRLPList[1].toUint()); // topic1 is from address + + require( + address(logTopicRLPList[2].toUint()) == address(0), // topic2 is to address + "MintableERC721Predicate: INVALID_RECEIVER" + ); + + IMintableERC721 token = IMintableERC721(rootToken); + + // RLP encoded tokenId, token metadata list ( in ordered form ) + bytes memory logData = logRLPList[2].toBytes(); + + (uint256[] memory tokenIds, bytes[] memory metaData) = abi.decode(logData, (uint256[], bytes[])); + require(tokenIds.length == metaData.length, "MintableERC721Predicate: TokenId & Metadata Length Mismatch"); + + uint256 length = tokenIds.length; + + for (uint256 i; i < length; i++) { + + uint256 tokenId = tokenIds[i]; + + // Check if token exists or not + // + // If does, transfer token to withdrawer + if (token.exists(tokenId)) { + + token.safeTransferFrom( + address(this), + withdrawer, + tokenId + ); + + } else { + + // If token was minted on L2 + // we'll mint it here, on L1, during + // exiting from L2, while setting its + // metadata passed from L2 + token.mint(withdrawer, tokenId, metadata[i]); + + } + + } + } else { // Attempting to exit with some event signature from L2, which is // not ( yet ) supported by L1 exit manager From 1376ec4011399d9ba6e2696dc19c499e1a7429e0 Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 18:49:54 +0530 Subject: [PATCH 06/16] chg: removed batch withdraw with metadata --- .../child/ChildToken/ChildMintableERC721.sol | 47 --------------- .../MintableERC721Predicate.sol | 59 ++----------------- 2 files changed, 5 insertions(+), 101 deletions(-) diff --git a/contracts/child/ChildToken/ChildMintableERC721.sol b/contracts/child/ChildToken/ChildMintableERC721.sol index 3edf92a8..2a10191a 100644 --- a/contracts/child/ChildToken/ChildMintableERC721.sol +++ b/contracts/child/ChildToken/ChildMintableERC721.sol @@ -22,7 +22,6 @@ contract ChildMintableERC721 is event WithdrawnBatch(address indexed user, uint256[] tokenIds); event TransferWithMetadata(address indexed from, address indexed to, uint256 indexed tokenId, bytes metaData); - event WithdrawBatchWithMetadata(address indexed from, address indexed to, uint256[] tokenIds, bytes[] metaData); constructor( string memory name_, @@ -141,52 +140,6 @@ contract ChildMintableERC721 is } - /** - * @notice called when user wants to withdraw tokens i.e. a batch of tokens, back to root chain with all respective metaData - * @dev Should handle withraw by burning user's token. - * Should set `withdrawnTokens` mapping to `true` for the tokenId being withdrawn - * This transaction will be verified when exiting on root chain - * - * @param tokenId tokenId to withdraw - */ - function withdrawBatchWithMetadata(uint256 calldata tokenIds) external { - - uint256 length = tokenIds.length; - require(length <= BATCH_LIMIT, "ChildMintableERC721: EXCEEDS_BATCH_LIMIT"); - - // During iteratively burning tokens, attempting - // to store respective metadata - // - // To be emitted with event, which will be - // verified on L1 & used for setting token metadata - // when exiting from L2 to L1 - uint256[length] memory metaData; - - // Iteratively burn ERC721 tokens, for performing - // batch withdraw - for (uint256 i; i < length; i++) { - - uint256 tokenId = tokenIds[i]; - - require(_msgSender() == ownerOf(tokenId), string(abi.encodePacked("ChildMintableERC721: INVALID_TOKEN_OWNER ", tokenId))); - // Marking token has been withdrawn - withdrawnTokens[tokenId] = true; - - // Storing metadata of respective token - // in ordered fashion - metaData[i] = this.encodeTokenMetadata(tokenId); - // Then burning token - _burn(tokenId); - - } - - // At last emit this event, which will be used - // in MintableERC721 predicate contract on L1 - // while verifying burn proof - emit WithdrawBatchWithMetadata(_msgSender(), address(0), tokenIds, metaData); - - } - /** * @notice This method is supposed to be called by client when withdrawing token with metadata * and pass return value of this function as second paramter of `withdrawWithMetadata` method diff --git a/contracts/root/TokenPredicates/MintableERC721Predicate.sol b/contracts/root/TokenPredicates/MintableERC721Predicate.sol index 46514743..453cf406 100644 --- a/contracts/root/TokenPredicates/MintableERC721Predicate.sol +++ b/contracts/root/TokenPredicates/MintableERC721Predicate.sol @@ -21,8 +21,9 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial bytes32 public constant WITHDRAW_BATCH_EVENT_SIG = 0xf871896b17e9cb7a64941c62c188a4f5c621b86800e3d15452ece01ce56073df; // keccak256("TransferWithMetadata(address,address,uint256,bytes)") bytes32 public constant TRANSFER_WITH_METADATA_EVENT_SIG = 0xf94915c6d1fd521cee85359239227480c7e8776d7caf1fc3bacad5c269b66a14; - // keccak256("WithdrawBatchWithMetadata(address,address,uint256[],bytes[])") - bytes32 public constant WITHDRAW_BATCH_WITH_METADATA_EVENT_SIG = 0x4f83879b5380fb2113c27cb03563cd911fe5f79f11b4ce5c470b40070c2acd86; + + // limit batching of tokens due to gas limit restrictions + uint256 public constant BATCH_LIMIT = 20; event LockedMintableERC721( address indexed depositor, @@ -90,7 +91,7 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial // Transferring token to this address, which will be // released when attempted to be unlocked - IERC721(rootToken).safeTransferFrom(depositor, address(this), tokenId); + IMintableERC721(rootToken).safeTransferFrom(depositor, address(this), tokenId); } else { // Locking a set a ERC721 token(s) @@ -110,7 +111,7 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial // to this predicate address for (uint256 i; i < length; i++) { - IERC721(rootToken).safeTransferFrom(depositor, address(this), tokenIds[i]); + IMintableERC721(rootToken).safeTransferFrom(depositor, address(this), tokenIds[i]); } @@ -237,56 +238,6 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial token.mint(withdrawer, tokenId, logRLPList[2].toBytes()); } - } else if (bytes32(logTopicRLPList[0].toUint()) == WITHDRAW_BATCH_WITH_METADATA_EVENT_SIG) { - // If this is batch NFT exit with all respective metadata 👆 - // - // Note: This is nothing but batch version of previous - // block of code - - address withdrawer = address(logTopicRLPList[1].toUint()); // topic1 is from address - - require( - address(logTopicRLPList[2].toUint()) == address(0), // topic2 is to address - "MintableERC721Predicate: INVALID_RECEIVER" - ); - - IMintableERC721 token = IMintableERC721(rootToken); - - // RLP encoded tokenId, token metadata list ( in ordered form ) - bytes memory logData = logRLPList[2].toBytes(); - - (uint256[] memory tokenIds, bytes[] memory metaData) = abi.decode(logData, (uint256[], bytes[])); - require(tokenIds.length == metaData.length, "MintableERC721Predicate: TokenId & Metadata Length Mismatch"); - - uint256 length = tokenIds.length; - - for (uint256 i; i < length; i++) { - - uint256 tokenId = tokenIds[i]; - - // Check if token exists or not - // - // If does, transfer token to withdrawer - if (token.exists(tokenId)) { - - token.safeTransferFrom( - address(this), - withdrawer, - tokenId - ); - - } else { - - // If token was minted on L2 - // we'll mint it here, on L1, during - // exiting from L2, while setting its - // metadata passed from L2 - token.mint(withdrawer, tokenId, metadata[i]); - - } - - } - } else { // Attempting to exit with some event signature from L2, which is // not ( yet ) supported by L1 exit manager From 42415b08876e7c2ba8e3afd630c7056967318cf0 Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 18:50:17 +0530 Subject: [PATCH 07/16] gen: flattened contracts --- flat/BaseRootTunnel.sol | 1 - flat/ChildMintableERC721.sol | 60 +++++++++++++++--- flat/MintableERC721Predicate.sol | 102 ++++++++++++++++++++++++++++--- flat/RootTunnel.sol | 1 - 4 files changed, 146 insertions(+), 18 deletions(-) diff --git a/flat/BaseRootTunnel.sol b/flat/BaseRootTunnel.sol index 95ee91de..ace6a494 100644 --- a/flat/BaseRootTunnel.sol +++ b/flat/BaseRootTunnel.sol @@ -1329,7 +1329,6 @@ pragma solidity ^0.6.6; - abstract contract BaseRootTunnel is AccessControlMixin { using RLPReader for bytes; using RLPReader for RLPReader.RLPItem; diff --git a/flat/ChildMintableERC721.sol b/flat/ChildMintableERC721.sol index 651d2108..5618fcf6 100644 --- a/flat/ChildMintableERC721.sol +++ b/flat/ChildMintableERC721.sol @@ -2120,6 +2120,10 @@ contract ChildMintableERC721 is bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); mapping (uint256 => bool) public withdrawnTokens; + // limit batching of tokens due to gas limit restrictions + uint256 public constant BATCH_LIMIT = 20; + + event WithdrawnBatch(address indexed user, uint256[] tokenIds); event TransferWithMetadata(address indexed from, address indexed to, uint256 indexed tokenId, bytes metaData); constructor( @@ -2147,20 +2151,34 @@ contract ChildMintableERC721 is /** * @notice called when token is deposited on root chain * @dev Should be callable only by ChildChainManager - * Should handle deposit by minting the required tokenId for user + * Should handle deposit by minting the required tokenId(s) for user * Should set `withdrawnTokens` mapping to `false` for the tokenId being deposited * Minting can also be done by other functions * @param user user address for whom deposit is being done - * @param depositData abi encoded tokenId + * @param depositData abi encoded tokenIds. Batch deposit also supported. */ function deposit(address user, bytes calldata depositData) external override only(DEPOSITOR_ROLE) { - uint256 tokenId = abi.decode(depositData, (uint256)); - withdrawnTokens[tokenId] = false; - _mint(user, tokenId); + + // deposit single + if (depositData.length == 32) { + uint256 tokenId = abi.decode(depositData, (uint256)); + withdrawnTokens[tokenId] = false; + _mint(user, tokenId); + + // deposit batch + } else { + uint256[] memory tokenIds = abi.decode(depositData, (uint256[])); + uint256 length = tokenIds.length; + for (uint256 i; i < length; i++) { + withdrawnTokens[tokenIds[i]] = false; + _mint(user, tokenIds[i]); + } + } + } /** @@ -2176,15 +2194,41 @@ contract ChildMintableERC721 is _burn(tokenId); } + /** + * @notice called when user wants to withdraw multiple tokens back to root chain + * @dev Should burn user's tokens. This transaction will be verified when exiting on root chain + * @param tokenIds tokenId list to withdraw + */ + function withdrawBatch(uint256[] calldata tokenIds) external { + + uint256 length = tokenIds.length; + require(length <= BATCH_LIMIT, "ChildMintableERC721: EXCEEDS_BATCH_LIMIT"); + + // Iteratively burn ERC721 tokens, for performing + // batch withdraw + for (uint256 i; i < length; i++) { + + uint256 tokenId = tokenIds[i]; + + require(_msgSender() == ownerOf(tokenId), string(abi.encodePacked("ChildMintableERC721: INVALID_TOKEN_OWNER ", tokenId))); + withdrawnTokens[tokenId] = true; + _burn(tokenId); + + } + + // At last emit this event, which will be used + // in MintableERC721 predicate contract on L1 + // while verifying burn proof + emit WithdrawnBatch(_msgSender(), tokenIds); + + } + /** * @notice called when user wants to withdraw token back to root chain with token URI * @dev Should handle withraw by burning user's token. * Should set `withdrawnTokens` mapping to `true` for the tokenId being withdrawn * This transaction will be verified when exiting on root chain * - * Before calling this function, you may want calling `encodeTokenMetadata` - * and get metadata to be transferred from L2 to L1 during exit - * * @param tokenId tokenId to withdraw */ function withdrawWithMetadata(uint256 tokenId) external { diff --git a/flat/MintableERC721Predicate.sol b/flat/MintableERC721Predicate.sol index 4f0ad4f9..33445dfd 100644 --- a/flat/MintableERC721Predicate.sol +++ b/flat/MintableERC721Predicate.sol @@ -1209,10 +1209,15 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial bytes32 public constant MANAGER_ROLE = 0x241ecf16d79d0f8dbfb92cbc07fe17840425976cf0667f022fe9877caa831b08; // keccak256("MintableERC721") bytes32 public constant TOKEN_TYPE = 0xd4392723c111fcb98b073fe55873efb447bcd23cd3e49ec9ea2581930cd01ddc; - // keccak("Transfer(address,address,uint256)") + // keccak256("Transfer(address,address,uint256)") bytes32 public constant TRANSFER_EVENT_SIG = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; - // keccak("TransferWithMetadata(address,address,uint256,string)") - bytes32 public constant TRANSFER_WITH_METADATA_EVENT_SIG = 0xf3c6803764de9a0fc1c2acb6a71f53407c5e2b9a3e04973e6586da23d64ecaa5; + // keccak256("WithdrawnBatch(address,uint256[])") + bytes32 public constant WITHDRAW_BATCH_EVENT_SIG = 0xf871896b17e9cb7a64941c62c188a4f5c621b86800e3d15452ece01ce56073df; + // keccak256("TransferWithMetadata(address,address,uint256,bytes)") + bytes32 public constant TRANSFER_WITH_METADATA_EVENT_SIG = 0xf94915c6d1fd521cee85359239227480c7e8776d7caf1fc3bacad5c269b66a14; + + // limit batching of tokens due to gas limit restrictions + uint256 public constant BATCH_LIMIT = 20; event LockedMintableERC721( address indexed depositor, @@ -1221,6 +1226,13 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial uint256 tokenId ); + event LockedMintableERC721Batch( + address indexed depositor, + address indexed depositReceiver, + address indexed rootToken, + uint256[] tokenIds + ); + constructor() public {} function initialize(address _owner) external initializer { @@ -1246,11 +1258,11 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial } /** - * @notice Lock ERC721 tokens for deposit, callable only by manager + * @notice Lock ERC721 token(s) for deposit, callable only by manager * @param depositor Address who wants to deposit token * @param depositReceiver Address (address) who wants to receive token on child chain * @param rootToken Token which gets deposited - * @param depositData ABI encoded tokenId + * @param depositData ABI encoded tokenId(s). It's possible to deposit batch of tokens. */ function lockTokens( address depositor, @@ -1262,9 +1274,43 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial override only(MANAGER_ROLE) { - uint256 tokenId = abi.decode(depositData, (uint256)); - emit LockedMintableERC721(depositor, depositReceiver, rootToken, tokenId); - IMintableERC721(rootToken).safeTransferFrom(depositor, address(this), tokenId); + + // Locking single ERC721 token + if (depositData.length == 32) { + + uint256 tokenId = abi.decode(depositData, (uint256)); + + // Emitting event that single token is getting locked in predicate + emit LockedMintableERC721(depositor, depositReceiver, rootToken, tokenId); + + // Transferring token to this address, which will be + // released when attempted to be unlocked + IMintableERC721(rootToken).safeTransferFrom(depositor, address(this), tokenId); + + } else { + // Locking a set a ERC721 token(s) + + uint256[] memory tokenIds = abi.decode(depositData, (uint256[])); + + // Emitting event that a set of ERC721 tokens are getting lockec + // in this predicate contract + emit LockedMintableERC721Batch(depositor, depositReceiver, rootToken, tokenIds); + + // These many tokens are attempted to be deposited + // by user + uint256 length = tokenIds.length; + require(length <= BATCH_LIMIT, "MintableERC721Predicate: EXCEEDS_BATCH_LIMIT"); + + // Iteratively trying to transfer ERC721 token + // to this predicate address + for (uint256 i; i < length; i++) { + + IMintableERC721(rootToken).safeTransferFrom(depositor, address(this), tokenIds[i]); + + } + + } + } /** @@ -1311,6 +1357,46 @@ contract MintableERC721Predicate is ITokenPredicate, AccessControlMixin, Initial token.mint(withdrawer, tokenId); } + } else if (bytes32(logTopicRLPList[0].toUint()) == WITHDRAW_BATCH_EVENT_SIG) { // topic0 is event sig + // If it's a simple batch exit, where a set of + // ERC721s were burnt in child chain with event signature + // looking like `WithdrawnBatch(address indexed user, uint256[] tokenIds);` + // + // @note This doesn't allow transfer of metadata cross chain + // For that check below `else if` block + + address withdrawer = address(logTopicRLPList[1].toUint()); // topic1 is from address + + // RLP encoded tokenId list + bytes memory logData = logRLPList[2].toBytes(); + + (uint256[] memory tokenIds) = abi.decode(logData, (uint256[])); + uint256 length = tokenIds.length; + + IMintableERC721 token = IMintableERC721(rootToken); + + for (uint256 i; i < length; i++) { + + uint256 tokenId = tokenIds[i]; + + // Check if token exists or not + // + // If does, transfer token to withdrawer + if (token.exists(tokenId)) { + token.safeTransferFrom( + address(this), + withdrawer, + tokenId + ); + } else { + // If token was minted on L2 + // we'll mint it here, on L1, during + // exiting from L2 + token.mint(withdrawer, tokenId); + } + + } + } else if (bytes32(logTopicRLPList[0].toUint()) == TRANSFER_WITH_METADATA_EVENT_SIG) { // If this is NFT exit with metadata i.e. URI 👆 // diff --git a/flat/RootTunnel.sol b/flat/RootTunnel.sol index 791a40ee..ec980274 100644 --- a/flat/RootTunnel.sol +++ b/flat/RootTunnel.sol @@ -1329,7 +1329,6 @@ pragma solidity ^0.6.6; - abstract contract BaseRootTunnel is AccessControlMixin { using RLPReader for bytes; using RLPReader for RLPReader.RLPItem; From d6b00ba4c7ad2cdab5d1c7a5e95dd88b8ac7c0b8 Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 18:50:36 +0530 Subject: [PATCH 08/16] gen: artifacts --- artifacts/ChildMintableERC721.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artifacts/ChildMintableERC721.json b/artifacts/ChildMintableERC721.json index bff1e644..122eb1f2 100644 --- a/artifacts/ChildMintableERC721.json +++ b/artifacts/ChildMintableERC721.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"address","name":"childChainManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address payable","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"metaData","type":"bytes"}],"name":"TransferWithMetadata","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSITOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERC712_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getDomainSeperator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"withdrawnTokens","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bytes","name":"depositData","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdrawWithMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"encodeTokenMetadata","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"}]} +{"abi":[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"address","name":"childChainManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address payable","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"metaData","type":"bytes"}],"name":"TransferWithMetadata","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"WithdrawnBatch","type":"event"},{"inputs":[],"name":"BATCH_LIMIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSITOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERC712_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getDomainSeperator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"withdrawnTokens","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bytes","name":"depositData","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"withdrawBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdrawWithMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"encodeTokenMetadata","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"}]} From eabd017286fbd3313ee666d618989e8393fcbe4f Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 19:38:53 +0530 Subject: [PATCH 09/16] new: metadata setter method in dummy root ERC721 contract --- contracts/root/RootToken/DummyERC721.sol | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/contracts/root/RootToken/DummyERC721.sol b/contracts/root/RootToken/DummyERC721.sol index d7331dc9..2e9a2881 100644 --- a/contracts/root/RootToken/DummyERC721.sol +++ b/contracts/root/RootToken/DummyERC721.sol @@ -1,18 +1,25 @@ pragma solidity 0.6.6; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {AccessControlMixin} from "../../common/AccessControlMixin.sol"; import {NativeMetaTransaction} from "../../common/NativeMetaTransaction.sol"; import {ContextMixin} from "../../common/ContextMixin.sol"; contract DummyERC721 is ERC721, + AccessControlMixin, NativeMetaTransaction, ContextMixin { + bytes32 public constant PREDICATE_ROLE = keccak256("PREDICATE_ROLE"); + constructor(string memory name_, string memory symbol_) public ERC721(name_, symbol_) { + _setupContractId("DummyERC721"); + _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); + _setupRole(PREDICATE_ROLE, _msgSender()); _initializeEIP712(name_); } @@ -20,6 +27,27 @@ contract DummyERC721 is _mint(_msgSender(), tokenId); } + /** + * If you're attempting to bring metadata associated with token + * from L2 to L1, you must implement this method + * + * To be invoked when attempting to exit ERC721 with metadata from L2 + * + * `data` is nothing but arbitrary byte array which + * is brought in L1, by event emitted in L2, during withdraw + */ + function setTokenMetadata(uint256 tokenId, bytes calldata data) external virtual only(PREDICATE_ROLE) { + // This function should decode metadata obtained from L2 + // and attempt to set it for this `tokenId` + // + // Following is just a default implementation, feel + // free to define your own encoding/ decoding scheme + // for L2 -> L1 token metadata transfer + string memory uri = abi.decode(data, (string)); + + _setTokenURI(tokenId, uri); + } + function _msgSender() internal override From 5360f9f4078c0612f98d6ee57f988735cd1c8c00 Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 19:58:54 +0530 Subject: [PATCH 10/16] new: interface root tokens are supposed to be implementing --- contracts/root/RootToken/IRootERC721.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 contracts/root/RootToken/IRootERC721.sol diff --git a/contracts/root/RootToken/IRootERC721.sol b/contracts/root/RootToken/IRootERC721.sol new file mode 100644 index 00000000..99f275f3 --- /dev/null +++ b/contracts/root/RootToken/IRootERC721.sol @@ -0,0 +1,12 @@ +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +pragma solidity 0.6.6; + +interface IRootERC721 is IERC721 { + + // Make sure you implement this method is root ERC721 + // contract when you're interested in transferring + // metadata from L2 to L1 + function setTokenMetadata(uint256 tokenId, bytes calldata data) external; + +} From 43da361a04b54700ddee4e6962e2d8408f88f6ea Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 20:15:43 +0530 Subject: [PATCH 11/16] new: ERC721 predicate having ability to handle burn with metadata signature --- contracts/root/RootToken/DummyERC721.sol | 13 ++++--- .../root/TokenPredicates/ERC721Predicate.sol | 36 ++++++++++++++++--- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/contracts/root/RootToken/DummyERC721.sol b/contracts/root/RootToken/DummyERC721.sol index 2e9a2881..3fdf9424 100644 --- a/contracts/root/RootToken/DummyERC721.sol +++ b/contracts/root/RootToken/DummyERC721.sol @@ -1,25 +1,21 @@ pragma solidity 0.6.6; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import {AccessControlMixin} from "../../common/AccessControlMixin.sol"; import {NativeMetaTransaction} from "../../common/NativeMetaTransaction.sol"; +import {IRootERC721} from "./IRootERC721.sol"; import {ContextMixin} from "../../common/ContextMixin.sol"; contract DummyERC721 is ERC721, AccessControlMixin, NativeMetaTransaction, + IRootERC721, ContextMixin { - bytes32 public constant PREDICATE_ROLE = keccak256("PREDICATE_ROLE"); - constructor(string memory name_, string memory symbol_) public ERC721(name_, symbol_) { - _setupContractId("DummyERC721"); - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - _setupRole(PREDICATE_ROLE, _msgSender()); _initializeEIP712(name_); } @@ -35,8 +31,11 @@ contract DummyERC721 is * * `data` is nothing but arbitrary byte array which * is brought in L1, by event emitted in L2, during withdraw + * + * @note Make sure this method is always callable by Predicate contract + * who will invoke it when attempting to exit with metadata */ - function setTokenMetadata(uint256 tokenId, bytes calldata data) external virtual only(PREDICATE_ROLE) { + function setTokenMetadata(uint256 tokenId, bytes calldata data) external virtual { // This function should decode metadata obtained from L2 // and attempt to set it for this `tokenId` // diff --git a/contracts/root/TokenPredicates/ERC721Predicate.sol b/contracts/root/TokenPredicates/ERC721Predicate.sol index d97fa247..f94ebbf5 100644 --- a/contracts/root/TokenPredicates/ERC721Predicate.sol +++ b/contracts/root/TokenPredicates/ERC721Predicate.sol @@ -1,6 +1,6 @@ pragma solidity 0.6.6; -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {IRootERC721} from "../RootToken/IRootERC721.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {RLPReader} from "../../lib/RLPReader.sol"; import {ITokenPredicate} from "./ITokenPredicate.sol"; @@ -13,8 +13,12 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); bytes32 public constant TOKEN_TYPE = keccak256("ERC721"); + // keccak256("Transfer(address,address,uint256)") bytes32 public constant TRANSFER_EVENT_SIG = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; + // keccak256("WithdrawnBatch(address,uint256[])") bytes32 public constant WITHDRAW_BATCH_EVENT_SIG = 0xf871896b17e9cb7a64941c62c188a4f5c621b86800e3d15452ece01ce56073df; + // keccak256("TransferWithMetadata(address,address,uint256,bytes)") + bytes32 public constant TRANSFER_WITH_METADATA_EVENT_SIG = 0xf94915c6d1fd521cee85359239227480c7e8776d7caf1fc3bacad5c269b66a14; // limit batching of tokens due to gas limit restrictions uint256 public constant BATCH_LIMIT = 20; @@ -77,7 +81,7 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, if (depositData.length == 32) { uint256 tokenId = abi.decode(depositData, (uint256)); emit LockedERC721(depositor, depositReceiver, rootToken, tokenId); - IERC721(rootToken).safeTransferFrom(depositor, address(this), tokenId); + IRootERC721(rootToken).safeTransferFrom(depositor, address(this), tokenId); // deposit batch } else { @@ -86,7 +90,7 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, uint256 length = tokenIds.length; require(length <= BATCH_LIMIT, "ERC721Predicate: EXCEEDS_BATCH_LIMIT"); for (uint256 i; i < length; i++) { - IERC721(rootToken).safeTransferFrom(depositor, address(this), tokenIds[i]); + IRootERC721(rootToken).safeTransferFrom(depositor, address(this), tokenIds[i]); } } } @@ -117,7 +121,7 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, "ERC721Predicate: INVALID_RECEIVER" ); - IERC721(rootToken).safeTransferFrom( + IRootERC721(rootToken).safeTransferFrom( address(this), withdrawer, logTopicRLPList[3].toUint() // topic3 is tokenId field @@ -128,9 +132,31 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, (uint256[] memory tokenIds) = abi.decode(logData, (uint256[])); // data is tokenId list uint256 length = tokenIds.length; for (uint256 i; i < length; i++) { - IERC721(rootToken).safeTransferFrom(address(this), withdrawer, tokenIds[i]); + IRootERC721(rootToken).safeTransferFrom(address(this), withdrawer, tokenIds[i]); } + } else if (bytes32(logTopicRLPList[0].toUint()) == TRANSFER_WITH_METADATA_EVENT_SIG) { + // If this is when NFT exit is done with arbitrary metadata on L2 + + address withdrawer = address(logTopicRLPList[1].toUint()); // topic1 is from address + + require( + address(logTopicRLPList[2].toUint()) == address(0), // topic2 is to address + "ERC721Predicate: INVALID_RECEIVER" + ); + + IERC721 token = IRootERC721(rootToken); + uint256 tokenId = logTopicRLPList[3].toUint(); // topic3 is tokenId field + + token.safeTransferFrom(address(this), withdrawer, tokenId); + // This function will be invoked for passing arbitrary + // metadata, obtained from event emitted in L2, to + // L1 ERC721, so that it can decode & do further processing + // + // @note Make sure you've implemented this method + // if you're interested in exiting with metadata + token.setTokenMetadata(tokenId, logRLPList[2].toBytes()); + } else { revert("ERC721Predicate: INVALID_SIGNATURE"); } From 49f48494126f993d4c53d7bc8ff1c97ed8853117 Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 20:16:45 +0530 Subject: [PATCH 12/16] new: dummy child ERC721 token with withdrawWithMetadata --- contracts/child/ChildToken/ChildERC721.sol | 43 ++++++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/contracts/child/ChildToken/ChildERC721.sol b/contracts/child/ChildToken/ChildERC721.sol index e53882be..1312d3f6 100644 --- a/contracts/child/ChildToken/ChildERC721.sol +++ b/contracts/child/ChildToken/ChildERC721.sol @@ -18,10 +18,8 @@ contract ChildERC721 is // limit batching of tokens due to gas limit restrictions uint256 public constant BATCH_LIMIT = 20; - event WithdrawnBatch( - address indexed user, - uint256[] tokenIds - ); + event WithdrawnBatch(address indexed user, uint256[] tokenIds); + event TransferWithMetadata(address indexed from, address indexed to, uint256 indexed tokenId, bytes metaData); constructor( string memory name_, @@ -98,4 +96,41 @@ contract ChildERC721 is } emit WithdrawnBatch(_msgSender(), tokenIds); } + + /** + * @notice called when user wants to withdraw token back to root chain with arbitrary metadata + * @dev Should handle withraw by burning user's token. + * + * This transaction will be verified when exiting on root chain + * + * @param tokenId tokenId to withdraw + */ + function withdrawWithMetadata(uint256 tokenId) external { + + require(_msgSender() == ownerOf(tokenId), "ChildERC721: INVALID_TOKEN_OWNER"); + + // Encoding metadata associated with tokenId & emitting event + emit TransferWithMetadata(_msgSender(), address(0), tokenId, this.encodeTokenMetadata(tokenId)); + + _burn(tokenId); + + } + + /** + * @notice This method is supposed to be called by client when withdrawing token with metadata + * and pass return value of this function as second paramter of `withdrawWithMetadata` method + * + * It can be overridden by clients to encode data in a different form, which needs to + * be decoded back by them correctly during exiting + * + * @param tokenId Token for which URI to be fetched + */ + function encodeTokenMetadata(uint256 tokenId) external view virtual returns (bytes memory) { + + // You're always free to change this default implementation + // and pack more data in byte array which can be decoded back + // in L1 + return abi.encode(tokenURI(tokenId)); + + } } From 3dc40e9de0b4649968429982bdaa463a8a4a91d9 Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 20:22:24 +0530 Subject: [PATCH 13/16] chg: properly overriding function --- contracts/root/RootToken/DummyERC721.sol | 5 ++--- contracts/root/TokenPredicates/ERC721Predicate.sol | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/contracts/root/RootToken/DummyERC721.sol b/contracts/root/RootToken/DummyERC721.sol index 3fdf9424..09725f2d 100644 --- a/contracts/root/RootToken/DummyERC721.sol +++ b/contracts/root/RootToken/DummyERC721.sol @@ -7,7 +7,6 @@ import {ContextMixin} from "../../common/ContextMixin.sol"; contract DummyERC721 is ERC721, - AccessControlMixin, NativeMetaTransaction, IRootERC721, ContextMixin @@ -32,10 +31,10 @@ contract DummyERC721 is * `data` is nothing but arbitrary byte array which * is brought in L1, by event emitted in L2, during withdraw * - * @note Make sure this method is always callable by Predicate contract + * Make sure this method is always callable by Predicate contract * who will invoke it when attempting to exit with metadata */ - function setTokenMetadata(uint256 tokenId, bytes calldata data) external virtual { + function setTokenMetadata(uint256 tokenId, bytes calldata data) external override { // This function should decode metadata obtained from L2 // and attempt to set it for this `tokenId` // diff --git a/contracts/root/TokenPredicates/ERC721Predicate.sol b/contracts/root/TokenPredicates/ERC721Predicate.sol index f94ebbf5..5f51f385 100644 --- a/contracts/root/TokenPredicates/ERC721Predicate.sol +++ b/contracts/root/TokenPredicates/ERC721Predicate.sol @@ -138,14 +138,12 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, } else if (bytes32(logTopicRLPList[0].toUint()) == TRANSFER_WITH_METADATA_EVENT_SIG) { // If this is when NFT exit is done with arbitrary metadata on L2 - address withdrawer = address(logTopicRLPList[1].toUint()); // topic1 is from address - require( address(logTopicRLPList[2].toUint()) == address(0), // topic2 is to address "ERC721Predicate: INVALID_RECEIVER" ); - IERC721 token = IRootERC721(rootToken); + IRootERC721 token = IRootERC721(rootToken); uint256 tokenId = logTopicRLPList[3].toUint(); // topic3 is tokenId field token.safeTransferFrom(address(this), withdrawer, tokenId); From 360e82950c2161755871993d2d4eebaef66263ea Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 20:22:40 +0530 Subject: [PATCH 14/16] gen: artifacts --- artifacts/ChildERC721.json | 2 +- artifacts/DummyERC721.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/artifacts/ChildERC721.json b/artifacts/ChildERC721.json index 9d34fe63..5ed68076 100644 --- a/artifacts/ChildERC721.json +++ b/artifacts/ChildERC721.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"address","name":"childChainManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address payable","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"WithdrawnBatch","type":"event"},{"inputs":[],"name":"BATCH_LIMIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSITOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERC712_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getDomainSeperator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bytes","name":"depositData","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"withdrawBatch","outputs":[],"stateMutability":"nonpayable","type":"function"}]} +{"abi":[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"address","name":"childChainManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address payable","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"metaData","type":"bytes"}],"name":"TransferWithMetadata","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"WithdrawnBatch","type":"event"},{"inputs":[],"name":"BATCH_LIMIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSITOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERC712_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getDomainSeperator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bytes","name":"depositData","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"withdrawBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdrawWithMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"encodeTokenMetadata","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"}]} diff --git a/artifacts/DummyERC721.json b/artifacts/DummyERC721.json index 975c6155..79b55a13 100644 --- a/artifacts/DummyERC721.json +++ b/artifacts/DummyERC721.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address payable","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"ERC712_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getDomainSeperator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"}]} +{"abi":[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address payable","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"ERC712_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getDomainSeperator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setTokenMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"}]} From 2cd513ed7081ffe29c49f7722c71dbb0aff5ef91 Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 20:22:52 +0530 Subject: [PATCH 15/16] gen: flattened contracts --- flat/ChildERC721.sol | 43 ++++++++++++++++++++++++++++++++++---- flat/DummyERC721.sol | 39 ++++++++++++++++++++++++++++++++++ flat/ERC721Predicate.sol | 45 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 119 insertions(+), 8 deletions(-) diff --git a/flat/ChildERC721.sol b/flat/ChildERC721.sol index 89d5e4e5..83b571eb 100644 --- a/flat/ChildERC721.sol +++ b/flat/ChildERC721.sol @@ -2121,10 +2121,8 @@ contract ChildERC721 is // limit batching of tokens due to gas limit restrictions uint256 public constant BATCH_LIMIT = 20; - event WithdrawnBatch( - address indexed user, - uint256[] tokenIds - ); + event WithdrawnBatch(address indexed user, uint256[] tokenIds); + event TransferWithMetadata(address indexed from, address indexed to, uint256 indexed tokenId, bytes metaData); constructor( string memory name_, @@ -2201,4 +2199,41 @@ contract ChildERC721 is } emit WithdrawnBatch(_msgSender(), tokenIds); } + + /** + * @notice called when user wants to withdraw token back to root chain with arbitrary metadata + * @dev Should handle withraw by burning user's token. + * + * This transaction will be verified when exiting on root chain + * + * @param tokenId tokenId to withdraw + */ + function withdrawWithMetadata(uint256 tokenId) external { + + require(_msgSender() == ownerOf(tokenId), "ChildERC721: INVALID_TOKEN_OWNER"); + + // Encoding metadata associated with tokenId & emitting event + emit TransferWithMetadata(_msgSender(), address(0), tokenId, this.encodeTokenMetadata(tokenId)); + + _burn(tokenId); + + } + + /** + * @notice This method is supposed to be called by client when withdrawing token with metadata + * and pass return value of this function as second paramter of `withdrawWithMetadata` method + * + * It can be overridden by clients to encode data in a different form, which needs to + * be decoded back by them correctly during exiting + * + * @param tokenId Token for which URI to be fetched + */ + function encodeTokenMetadata(uint256 tokenId) external view virtual returns (bytes memory) { + + // You're always free to change this default implementation + // and pack more data in byte array which can be decoded back + // in L1 + return abi.encode(tokenURI(tokenId)); + + } } diff --git a/flat/DummyERC721.sol b/flat/DummyERC721.sol index 8af96fcc..3bbec2fd 100644 --- a/flat/DummyERC721.sol +++ b/flat/DummyERC721.sol @@ -1826,6 +1826,19 @@ contract NativeMetaTransaction is EIP712Base { } } +// File: contracts/root/RootToken/IRootERC721.sol + +pragma solidity 0.6.6; + +interface IRootERC721 is IERC721 { + + // Make sure you implement this method is root ERC721 + // contract when you're interested in transferring + // metadata from L2 to L1 + function setTokenMetadata(uint256 tokenId, bytes calldata data) external; + +} + // File: contracts/common/ContextMixin.sol pragma solidity 0.6.6; @@ -1860,9 +1873,11 @@ pragma solidity 0.6.6; + contract DummyERC721 is ERC721, NativeMetaTransaction, + IRootERC721, ContextMixin { constructor(string memory name_, string memory symbol_) @@ -1876,6 +1891,30 @@ contract DummyERC721 is _mint(_msgSender(), tokenId); } + /** + * If you're attempting to bring metadata associated with token + * from L2 to L1, you must implement this method + * + * To be invoked when attempting to exit ERC721 with metadata from L2 + * + * `data` is nothing but arbitrary byte array which + * is brought in L1, by event emitted in L2, during withdraw + * + * Make sure this method is always callable by Predicate contract + * who will invoke it when attempting to exit with metadata + */ + function setTokenMetadata(uint256 tokenId, bytes calldata data) external override { + // This function should decode metadata obtained from L2 + // and attempt to set it for this `tokenId` + // + // Following is just a default implementation, feel + // free to define your own encoding/ decoding scheme + // for L2 -> L1 token metadata transfer + string memory uri = abi.decode(data, (string)); + + _setTokenURI(tokenId, uri); + } + function _msgSender() internal override diff --git a/flat/ERC721Predicate.sol b/flat/ERC721Predicate.sol index 5875d7d8..583bac2e 100644 --- a/flat/ERC721Predicate.sol +++ b/flat/ERC721Predicate.sol @@ -157,6 +157,19 @@ interface IERC721 is IERC165 { function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; } +// File: contracts/root/RootToken/IRootERC721.sol + +pragma solidity 0.6.6; + +interface IRootERC721 is IERC721 { + + // Make sure you implement this method is root ERC721 + // contract when you're interested in transferring + // metadata from L2 to L1 + function setTokenMetadata(uint256 tokenId, bytes calldata data) external; + +} + // File: @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol // SPDX-License-Identifier: MIT @@ -1172,8 +1185,12 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); bytes32 public constant TOKEN_TYPE = keccak256("ERC721"); + // keccak256("Transfer(address,address,uint256)") bytes32 public constant TRANSFER_EVENT_SIG = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; + // keccak256("WithdrawnBatch(address,uint256[])") bytes32 public constant WITHDRAW_BATCH_EVENT_SIG = 0xf871896b17e9cb7a64941c62c188a4f5c621b86800e3d15452ece01ce56073df; + // keccak256("TransferWithMetadata(address,address,uint256,bytes)") + bytes32 public constant TRANSFER_WITH_METADATA_EVENT_SIG = 0xf94915c6d1fd521cee85359239227480c7e8776d7caf1fc3bacad5c269b66a14; // limit batching of tokens due to gas limit restrictions uint256 public constant BATCH_LIMIT = 20; @@ -1236,7 +1253,7 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, if (depositData.length == 32) { uint256 tokenId = abi.decode(depositData, (uint256)); emit LockedERC721(depositor, depositReceiver, rootToken, tokenId); - IERC721(rootToken).safeTransferFrom(depositor, address(this), tokenId); + IRootERC721(rootToken).safeTransferFrom(depositor, address(this), tokenId); // deposit batch } else { @@ -1245,7 +1262,7 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, uint256 length = tokenIds.length; require(length <= BATCH_LIMIT, "ERC721Predicate: EXCEEDS_BATCH_LIMIT"); for (uint256 i; i < length; i++) { - IERC721(rootToken).safeTransferFrom(depositor, address(this), tokenIds[i]); + IRootERC721(rootToken).safeTransferFrom(depositor, address(this), tokenIds[i]); } } } @@ -1276,7 +1293,7 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, "ERC721Predicate: INVALID_RECEIVER" ); - IERC721(rootToken).safeTransferFrom( + IRootERC721(rootToken).safeTransferFrom( address(this), withdrawer, logTopicRLPList[3].toUint() // topic3 is tokenId field @@ -1287,9 +1304,29 @@ contract ERC721Predicate is ITokenPredicate, AccessControlMixin, Initializable, (uint256[] memory tokenIds) = abi.decode(logData, (uint256[])); // data is tokenId list uint256 length = tokenIds.length; for (uint256 i; i < length; i++) { - IERC721(rootToken).safeTransferFrom(address(this), withdrawer, tokenIds[i]); + IRootERC721(rootToken).safeTransferFrom(address(this), withdrawer, tokenIds[i]); } + } else if (bytes32(logTopicRLPList[0].toUint()) == TRANSFER_WITH_METADATA_EVENT_SIG) { + // If this is when NFT exit is done with arbitrary metadata on L2 + + require( + address(logTopicRLPList[2].toUint()) == address(0), // topic2 is to address + "ERC721Predicate: INVALID_RECEIVER" + ); + + IRootERC721 token = IRootERC721(rootToken); + uint256 tokenId = logTopicRLPList[3].toUint(); // topic3 is tokenId field + + token.safeTransferFrom(address(this), withdrawer, tokenId); + // This function will be invoked for passing arbitrary + // metadata, obtained from event emitted in L2, to + // L1 ERC721, so that it can decode & do further processing + // + // @note Make sure you've implemented this method + // if you're interested in exiting with metadata + token.setTokenMetadata(tokenId, logRLPList[2].toBytes()); + } else { revert("ERC721Predicate: INVALID_SIGNATURE"); } From 701a93b8fb2870cc7f2234e21210d11cab8de145 Mon Sep 17 00:00:00 2001 From: Anjan Roy Date: Tue, 9 Mar 2021 20:27:32 +0530 Subject: [PATCH 16/16] fix: corrected event signature --- test/helpers/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helpers/constants.js b/test/helpers/constants.js index 206468c2..6dbe2145 100644 --- a/test/helpers/constants.js +++ b/test/helpers/constants.js @@ -17,7 +17,7 @@ export const etherAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' export const erc20TransferEventSig = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' export const erc721TransferEventSig = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' -export const erc721TransferWithMetadataEventSig = '0xf3c6803764de9a0fc1c2acb6a71f53407c5e2b9a3e04973e6586da23d64ecaa5' +export const erc721TransferWithMetadataEventSig = '0xf94915c6d1fd521cee85359239227480c7e8776d7caf1fc3bacad5c269b66a14' export const erc1155TransferSingleEventSig = '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62' export const erc1155TransferBatchEventSig = '0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb'