Skip to content

Commit 2faeee3

Browse files
authored
Merge pull request #77 from maticnetwork/custom-erc1155-predicate-tsb
Custom ERC1155Predicate with generic ChainExit event
2 parents dabb08e + ff5ccc2 commit 2faeee3

13 files changed

+2264
-3
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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"}]}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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"}]}
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
pragma solidity 0.6.6;
2+
3+
import {IMintableERC1155} from "../RootToken/IMintableERC1155.sol";
4+
import {
5+
ERC1155Receiver
6+
} from "@openzeppelin/contracts/token/ERC1155/ERC1155Receiver.sol";
7+
import {AccessControlMixin} from "../../common/AccessControlMixin.sol";
8+
import {RLPReader} from "../../lib/RLPReader.sol";
9+
import {ITokenPredicate} from "./ITokenPredicate.sol";
10+
import {Initializable} from "../../common/Initializable.sol";
11+
12+
contract ChainExitERC1155Predicate is
13+
ITokenPredicate,
14+
ERC1155Receiver,
15+
AccessControlMixin,
16+
Initializable
17+
{
18+
using RLPReader for bytes;
19+
using RLPReader for RLPReader.RLPItem;
20+
21+
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
22+
bytes32 public constant TOKEN_TYPE = keccak256("ChainExitERC1155");
23+
// Only this event is considered in exit function : ChainExit(address indexed to, uint256[] tokenId, uint256[] amount, bytes data)
24+
bytes32 public constant CHAIN_EXIT_EVENT_SIG = keccak256("ChainExit(address,uint256[],uint256[],bytes)");
25+
26+
event LockedBatchChainExitERC1155(
27+
address indexed depositor,
28+
address indexed depositReceiver,
29+
address indexed rootToken,
30+
uint256[] ids,
31+
uint256[] amounts
32+
);
33+
34+
constructor() public {}
35+
36+
function initialize(address _owner) external initializer {
37+
_setupContractId("ChainExitERC1155Predicate");
38+
_setupRole(DEFAULT_ADMIN_ROLE, _owner);
39+
_setupRole(MANAGER_ROLE, _owner);
40+
}
41+
42+
/**
43+
* @notice rejects single transfer
44+
*/
45+
function onERC1155Received(
46+
address,
47+
address,
48+
uint256,
49+
uint256,
50+
bytes calldata
51+
) external override returns (bytes4) {
52+
return 0;
53+
}
54+
55+
/**
56+
* @notice accepts batch transfer
57+
*/
58+
function onERC1155BatchReceived(
59+
address,
60+
address,
61+
uint256[] calldata,
62+
uint256[] calldata,
63+
bytes calldata
64+
) external override returns (bytes4) {
65+
return ERC1155Receiver(0).onERC1155BatchReceived.selector;
66+
}
67+
68+
/**
69+
* @notice Lock ERC1155 tokens for deposit, callable only by manager
70+
* @param depositor Address who wants to deposit tokens
71+
* @param depositReceiver Address (address) who wants to receive tokens on child chain
72+
* @param rootToken Token which gets deposited
73+
* @param depositData ABI encoded id array and amount array
74+
*/
75+
function lockTokens(
76+
address depositor,
77+
address depositReceiver,
78+
address rootToken,
79+
bytes calldata depositData
80+
) external override only(MANAGER_ROLE) {
81+
// forcing batch deposit since supporting both single and batch deposit introduces too much complexity
82+
(
83+
uint256[] memory ids,
84+
uint256[] memory amounts,
85+
bytes memory data
86+
) = abi.decode(depositData, (uint256[], uint256[], bytes));
87+
88+
emit LockedBatchChainExitERC1155(
89+
depositor,
90+
depositReceiver,
91+
rootToken,
92+
ids,
93+
amounts
94+
);
95+
IMintableERC1155(rootToken).safeBatchTransferFrom(
96+
depositor,
97+
address(this),
98+
ids,
99+
amounts,
100+
data
101+
);
102+
}
103+
104+
/**
105+
* @notice Creates an array of `size` by repeating provided address,
106+
* to be required for passing to batched balance checking function of ERC1155 tokens.
107+
* @param addr Address to be repeated `size` times in resulting array
108+
* @param size Size of resulting array
109+
*/
110+
function makeArrayWithAddress(address addr, uint256 size)
111+
internal
112+
pure
113+
returns (address[] memory)
114+
{
115+
require(
116+
addr != address(0),
117+
"ChainExitERC1155Predicate: Invalid address"
118+
);
119+
require(
120+
size > 0,
121+
"ChainExitERC1155Predicate: Invalid resulting array length"
122+
);
123+
124+
address[] memory addresses = new address[](size);
125+
126+
for (uint256 i = 0; i < size; i++) {
127+
addresses[i] = addr;
128+
}
129+
130+
return addresses;
131+
}
132+
133+
/**
134+
* @notice Calculates amount of tokens to be minted, by subtracting available
135+
* token balances from amount of tokens to be exited
136+
* @param balances Token balances this contract holds for some ordered token ids
137+
* @param exitAmounts Amount of tokens being exited
138+
*/
139+
function calculateAmountsToBeMinted(
140+
uint256[] memory balances,
141+
uint256[] memory exitAmounts
142+
) internal pure returns (uint256[] memory, bool, bool) {
143+
uint256 count = balances.length;
144+
require(
145+
count == exitAmounts.length,
146+
"ChainExitERC1155Predicate: Array length mismatch found"
147+
);
148+
149+
uint256[] memory toBeMinted = new uint256[](count);
150+
bool needMintStep;
151+
bool needTransferStep;
152+
153+
for (uint256 i = 0; i < count; i++) {
154+
if (balances[i] < exitAmounts[i]) {
155+
toBeMinted[i] = exitAmounts[i] - balances[i];
156+
needMintStep = true;
157+
}
158+
159+
if(balances[i] != 0) {
160+
needTransferStep = true;
161+
}
162+
}
163+
164+
return (toBeMinted, needMintStep, needTransferStep);
165+
}
166+
167+
/**
168+
* @notice Validates log signature, withdrawer address
169+
* then sends the correct tokenId, amount to withdrawer
170+
* callable only by manager
171+
* @param rootToken Token which gets withdrawn
172+
* @param log Valid ChainExit log from child chain
173+
*/
174+
function exitTokens(
175+
address,
176+
address rootToken,
177+
bytes memory log
178+
) public override only(MANAGER_ROLE) {
179+
RLPReader.RLPItem[] memory logRLPList = log.toRlpItem().toList();
180+
RLPReader.RLPItem[] memory logTopicRLPList = logRLPList[1].toList();
181+
bytes memory logData = logRLPList[2].toBytes();
182+
183+
if (bytes32(logTopicRLPList[0].toUint()) == CHAIN_EXIT_EVENT_SIG) {
184+
185+
address withdrawer = address(logTopicRLPList[1].toUint());
186+
require(withdrawer != address(0), "ChainExitERC1155Predicate: INVALID_RECEIVER");
187+
188+
(uint256[] memory ids, uint256[] memory amounts, bytes memory data) = abi.decode(
189+
logData,
190+
(uint256[], uint256[], bytes)
191+
);
192+
193+
IMintableERC1155 token = IMintableERC1155(rootToken);
194+
195+
uint256[] memory balances = token.balanceOfBatch(makeArrayWithAddress(address(this), ids.length), ids);
196+
(uint256[] memory toBeMinted, bool needMintStep, bool needTransferStep) = calculateAmountsToBeMinted(balances, amounts);
197+
198+
if(needMintStep) {
199+
token.mintBatch(
200+
withdrawer,
201+
ids,
202+
toBeMinted,
203+
data // passing data when minting to withdrawer
204+
);
205+
}
206+
207+
if(needTransferStep) {
208+
token.safeBatchTransferFrom(
209+
address(this),
210+
withdrawer,
211+
ids,
212+
balances,
213+
data // passing data when transferring unlocked tokens to withdrawer
214+
);
215+
}
216+
217+
} else {
218+
revert("ChainExitERC1155Predicate: INVALID_WITHDRAW_SIG");
219+
}
220+
}
221+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pragma solidity 0.6.6;
2+
3+
import {UpgradableProxy} from "../../common/Proxy/UpgradableProxy.sol";
4+
5+
contract ChainExitERC1155PredicateProxy is UpgradableProxy {
6+
constructor(address _proxyTo)
7+
public
8+
UpgradableProxy(_proxyTo)
9+
{}
10+
}

0 commit comments

Comments
 (0)