Status : Draft
Abstract : Proposing generic & simpler design of Predicate Contracts for ERC20, ERC721, ERC1155, which will be able to provide all of standard, mintable, burnable functionalities, while also allowing full-duplex arbitrary data passing during deposit & withdraw.
Motivation : Currently we’ve standard, mintable, burnable predicates for each of ERC20, ERC721, ERC1155, which allows developers to deposit, withdraw, mint on L2, burn on L2. For implementing single & batch withdraw of ERC721, ERC1155 tokens, predicates expect several event signatures in L2 transaction, which makes predicate logic repetitive & complex. A subset of those predicates also support uni-directional arbitrary data passing i.e. during exit.
Following proposed design can be used for writing generic predicates which will support all of aforementioned functionalities ( at cost of lower complexity ) along with bi-directional arbitrary data passing - giving developers more freedom in building multi-chain applications.
Specification : Predicates are expected to implement following interface
interface ITokenPredicate {
function lockTokens(
address depositor,
address depositReceiver,
address rootToken,
bytes calldata depositData
) external;
function exitTokens(
address sender,
address rootToken,
bytes calldata logRLPList
) external;
}
GenericERC20Predicate’s lockTokens
method is expected to deserialise depositData
as below, where
- amount : Tokens being deposited
- data : Arbitrary length, byte serialised, application specific data
(uint256 amount, bytes memory data) = abi.decode(depositData, (uint256, bytes))
exitTokens
expects to see only one event signature in logRLPList
, otherwise it’ll revert
ExitERC20(address to, uint256 amount, bytes data)
It processes exit event as per following logic
if (to == address(0)) {
// burnability
ERC20.burn(address(this), amount, data);
return;
}
uint256 balance = ERC20.balanceOf(address(this))
if (balance < amount) {
// mintability
ERC20.mint(address(this), amount - balance);
}
ERC20.transfer(to, amount, data);
GenericERC721Predicate allows batch deposit, while expecting depositData
in form
- ids : Array of token ids being deposited
- data : Arbitrary length, byte serialised, application specific data
(uint256[] memory ids, bytes memory data) = abi.decode(depositData, (uint256[], bytes))
Same as GenericERC20Predicate, it supports only one event signature in exitTokens
method
ExitERC721(address to, uint256[] ids, bytes data)
Logic flow to be followed during exit is quite same as GenericERC20Predicate
if (to == address(0)) {
// burnability
ERC721.burnBatch(address(this), ids, data);
return;
}
for (uint256 i = 0; i < ids.length; i++) {
if(ERC721.ownerOf(ids[i]) != address(this)) {
// mintability
ERC721.mint(address(this), ids[i]);
}
}
ERC721.transferBatch(to, ids, data);
Very similarly GenericERC1155Predicate allows arbitrary data passing during batch deposit, where it deserialises depositData
as below
(uint256[] memory ids, uint256[] memory amounts, bytes memory data) = abi.decode(depositData, (uint256[], uint256[], bytes))
Its exitTokens
method only expects to see event signature
ExitERC1155(address to, uint256[] ids, uint256[] amounts, bytes data)
Otherwise it simply reverts.
Exit happens by following rule
if (to == address(0)) {
// burnability
ERC1155.burnBatch(address(this), ids, data);
return;
}
for (uint256 i = 0; i < ids.length; i++) {
uint256 balance = ERC1155.balanceOf(ids[i], address(this))
if(balance < amounts[i]) {
// mintability
ERC1155.mint(address(this), amounts[i] - balance);
}
}
ERC1155.transferBatch(to, ids, amounts, data);
Rationale : This predicate design attempts to lower cognitive load on both predicate developers & predicate users by encapsulating all functionalities into single predicate for each token type, while expecting predicate users to implement a subset of methods ( only those they need to ) on respective L1, L2 tokens.
Backwards Compatibility : These new predicates are incompatible with already mapped tokens, so they can’t just be remapped to use new predicates. Potential rewrite of those L1, L2 tokens are required for using new functionalities.
Security Considerations : This design doesn’t loosen security assumptions anyhow, it allows exiting from L2 only when supported event signature is produced & that event log is included in receiptRootHash
submitted by L2 validators during checkpointing.