-
Notifications
You must be signed in to change notification settings - Fork 260
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #77 from maticnetwork/custom-erc1155-predicate-tsb
Custom ERC1155Predicate with generic ChainExit event
- Loading branch information
Showing
13 changed files
with
2,264 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"depositReceiver","type":"address"},{"indexed":true,"internalType":"address","name":"rootToken","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"LockedBatchChainExitERC1155","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"},{"inputs":[],"name":"CHAIN_EXIT_EVENT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_TYPE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"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":"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":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"depositor","type":"address"},{"internalType":"address","name":"depositReceiver","type":"address"},{"internalType":"address","name":"rootToken","type":"address"},{"internalType":"bytes","name":"depositData","type":"bytes"}],"name":"lockTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"rootToken","type":"address"},{"internalType":"bytes","name":"log","type":"bytes"}],"name":"exitTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"abi":[{"inputs":[{"internalType":"address","name":"_proxyTo","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_new","type":"address"},{"indexed":false,"internalType":"address","name":"_old","type":"address"}],"name":"ProxyOwnerUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_new","type":"address"},{"indexed":true,"internalType":"address","name":"_old","type":"address"}],"name":"ProxyUpdated","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxyOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxyType","outputs":[{"internalType":"uint256","name":"proxyTypeId","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferProxyOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newProxyTo","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"updateAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_newProxyTo","type":"address"}],"name":"updateImplementation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]} |
221 changes: 221 additions & 0 deletions
221
contracts/root/TokenPredicates/ChainExitERC1155Predicate.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
pragma solidity 0.6.6; | ||
|
||
import {IMintableERC1155} from "../RootToken/IMintableERC1155.sol"; | ||
import { | ||
ERC1155Receiver | ||
} from "@openzeppelin/contracts/token/ERC1155/ERC1155Receiver.sol"; | ||
import {AccessControlMixin} from "../../common/AccessControlMixin.sol"; | ||
import {RLPReader} from "../../lib/RLPReader.sol"; | ||
import {ITokenPredicate} from "./ITokenPredicate.sol"; | ||
import {Initializable} from "../../common/Initializable.sol"; | ||
|
||
contract ChainExitERC1155Predicate is | ||
ITokenPredicate, | ||
ERC1155Receiver, | ||
AccessControlMixin, | ||
Initializable | ||
{ | ||
using RLPReader for bytes; | ||
using RLPReader for RLPReader.RLPItem; | ||
|
||
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); | ||
bytes32 public constant TOKEN_TYPE = keccak256("ChainExitERC1155"); | ||
// Only this event is considered in exit function : ChainExit(address indexed to, uint256[] tokenId, uint256[] amount, bytes data) | ||
bytes32 public constant CHAIN_EXIT_EVENT_SIG = keccak256("ChainExit(address,uint256[],uint256[],bytes)"); | ||
|
||
event LockedBatchChainExitERC1155( | ||
address indexed depositor, | ||
address indexed depositReceiver, | ||
address indexed rootToken, | ||
uint256[] ids, | ||
uint256[] amounts | ||
); | ||
|
||
constructor() public {} | ||
|
||
function initialize(address _owner) external initializer { | ||
_setupContractId("ChainExitERC1155Predicate"); | ||
_setupRole(DEFAULT_ADMIN_ROLE, _owner); | ||
_setupRole(MANAGER_ROLE, _owner); | ||
} | ||
|
||
/** | ||
* @notice rejects single transfer | ||
*/ | ||
function onERC1155Received( | ||
address, | ||
address, | ||
uint256, | ||
uint256, | ||
bytes calldata | ||
) external override returns (bytes4) { | ||
return 0; | ||
} | ||
|
||
/** | ||
* @notice accepts batch transfer | ||
*/ | ||
function onERC1155BatchReceived( | ||
address, | ||
address, | ||
uint256[] calldata, | ||
uint256[] calldata, | ||
bytes calldata | ||
) external override returns (bytes4) { | ||
return ERC1155Receiver(0).onERC1155BatchReceived.selector; | ||
} | ||
|
||
/** | ||
* @notice Lock ERC1155 tokens for deposit, callable only by manager | ||
* @param depositor Address who wants to deposit tokens | ||
* @param depositReceiver Address (address) who wants to receive tokens on child chain | ||
* @param rootToken Token which gets deposited | ||
* @param depositData ABI encoded id array and amount array | ||
*/ | ||
function lockTokens( | ||
address depositor, | ||
address depositReceiver, | ||
address rootToken, | ||
bytes calldata depositData | ||
) external override only(MANAGER_ROLE) { | ||
// forcing batch deposit since supporting both single and batch deposit introduces too much complexity | ||
( | ||
uint256[] memory ids, | ||
uint256[] memory amounts, | ||
bytes memory data | ||
) = abi.decode(depositData, (uint256[], uint256[], bytes)); | ||
|
||
emit LockedBatchChainExitERC1155( | ||
depositor, | ||
depositReceiver, | ||
rootToken, | ||
ids, | ||
amounts | ||
); | ||
IMintableERC1155(rootToken).safeBatchTransferFrom( | ||
depositor, | ||
address(this), | ||
ids, | ||
amounts, | ||
data | ||
); | ||
} | ||
|
||
/** | ||
* @notice Creates an array of `size` by repeating provided address, | ||
* to be required for passing to batched balance checking function of ERC1155 tokens. | ||
* @param addr Address to be repeated `size` times in resulting array | ||
* @param size Size of resulting array | ||
*/ | ||
function makeArrayWithAddress(address addr, uint256 size) | ||
internal | ||
pure | ||
returns (address[] memory) | ||
{ | ||
require( | ||
addr != address(0), | ||
"ChainExitERC1155Predicate: Invalid address" | ||
); | ||
require( | ||
size > 0, | ||
"ChainExitERC1155Predicate: Invalid resulting array length" | ||
); | ||
|
||
address[] memory addresses = new address[](size); | ||
|
||
for (uint256 i = 0; i < size; i++) { | ||
addresses[i] = addr; | ||
} | ||
|
||
return addresses; | ||
} | ||
|
||
/** | ||
* @notice Calculates amount of tokens to be minted, by subtracting available | ||
* token balances from amount of tokens to be exited | ||
* @param balances Token balances this contract holds for some ordered token ids | ||
* @param exitAmounts Amount of tokens being exited | ||
*/ | ||
function calculateAmountsToBeMinted( | ||
uint256[] memory balances, | ||
uint256[] memory exitAmounts | ||
) internal pure returns (uint256[] memory, bool, bool) { | ||
uint256 count = balances.length; | ||
require( | ||
count == exitAmounts.length, | ||
"ChainExitERC1155Predicate: Array length mismatch found" | ||
); | ||
|
||
uint256[] memory toBeMinted = new uint256[](count); | ||
bool needMintStep; | ||
bool needTransferStep; | ||
|
||
for (uint256 i = 0; i < count; i++) { | ||
if (balances[i] < exitAmounts[i]) { | ||
toBeMinted[i] = exitAmounts[i] - balances[i]; | ||
needMintStep = true; | ||
} | ||
|
||
if(balances[i] != 0) { | ||
needTransferStep = true; | ||
} | ||
} | ||
|
||
return (toBeMinted, needMintStep, needTransferStep); | ||
} | ||
|
||
/** | ||
* @notice Validates log signature, withdrawer address | ||
* then sends the correct tokenId, amount to withdrawer | ||
* callable only by manager | ||
* @param rootToken Token which gets withdrawn | ||
* @param log Valid ChainExit log from child chain | ||
*/ | ||
function exitTokens( | ||
address, | ||
address rootToken, | ||
bytes memory log | ||
) public override only(MANAGER_ROLE) { | ||
RLPReader.RLPItem[] memory logRLPList = log.toRlpItem().toList(); | ||
RLPReader.RLPItem[] memory logTopicRLPList = logRLPList[1].toList(); | ||
bytes memory logData = logRLPList[2].toBytes(); | ||
|
||
if (bytes32(logTopicRLPList[0].toUint()) == CHAIN_EXIT_EVENT_SIG) { | ||
|
||
address withdrawer = address(logTopicRLPList[1].toUint()); | ||
require(withdrawer != address(0), "ChainExitERC1155Predicate: INVALID_RECEIVER"); | ||
|
||
(uint256[] memory ids, uint256[] memory amounts, bytes memory data) = abi.decode( | ||
logData, | ||
(uint256[], uint256[], bytes) | ||
); | ||
|
||
IMintableERC1155 token = IMintableERC1155(rootToken); | ||
|
||
uint256[] memory balances = token.balanceOfBatch(makeArrayWithAddress(address(this), ids.length), ids); | ||
(uint256[] memory toBeMinted, bool needMintStep, bool needTransferStep) = calculateAmountsToBeMinted(balances, amounts); | ||
|
||
if(needMintStep) { | ||
token.mintBatch( | ||
withdrawer, | ||
ids, | ||
toBeMinted, | ||
data // passing data when minting to withdrawer | ||
); | ||
} | ||
|
||
if(needTransferStep) { | ||
token.safeBatchTransferFrom( | ||
address(this), | ||
withdrawer, | ||
ids, | ||
balances, | ||
data // passing data when transferring unlocked tokens to withdrawer | ||
); | ||
} | ||
|
||
} else { | ||
revert("ChainExitERC1155Predicate: INVALID_WITHDRAW_SIG"); | ||
} | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
contracts/root/TokenPredicates/ChainExitERC1155PredicateProxy.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
pragma solidity 0.6.6; | ||
|
||
import {UpgradableProxy} from "../../common/Proxy/UpgradableProxy.sol"; | ||
|
||
contract ChainExitERC1155PredicateProxy is UpgradableProxy { | ||
constructor(address _proxyTo) | ||
public | ||
UpgradableProxy(_proxyTo) | ||
{} | ||
} |
Oops, something went wrong.