Skip to content

Commit

Permalink
Merge pull request #77 from maticnetwork/custom-erc1155-predicate-tsb
Browse files Browse the repository at this point in the history
Custom ERC1155Predicate with generic ChainExit event
  • Loading branch information
itzmeanjan committed Aug 30, 2021
2 parents dabb08e + ff5ccc2 commit 2faeee3
Show file tree
Hide file tree
Showing 13 changed files with 2,264 additions and 3 deletions.
1 change: 1 addition & 0 deletions artifacts/ChainExitERC1155Predicate.json
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"}]}
1 change: 1 addition & 0 deletions artifacts/ChainExitERC1155PredicateProxy.json
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 contracts/root/TokenPredicates/ChainExitERC1155Predicate.sol
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 contracts/root/TokenPredicates/ChainExitERC1155PredicateProxy.sol
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)
{}
}
Loading

0 comments on commit 2faeee3

Please sign in to comment.