From cbbbadf1938484bd47082a843751c85433f14ec6 Mon Sep 17 00:00:00 2001 From: 0xAshish Date: Fri, 7 Feb 2020 15:49:06 +0530 Subject: [PATCH] fix lint :D --- contracts/Migrations.sol | 31 +- contracts/child/BaseERC20.sol | 139 +- contracts/child/ChildChain.sol | 286 ++-- contracts/child/ChildERC20.sol | 125 +- contracts/child/ChildERC721.sol | 199 +-- contracts/child/ChildERC721Mintable.sol | 25 +- contracts/child/ChildToken.sol | 100 +- contracts/child/MaticChildERC20.sol | 179 +-- contracts/child/bor/StateReceiver.sol | 2 +- contracts/child/bor/StateSyncerVerifier.sol | 71 +- contracts/child/misc/EIP712.sol | 96 +- contracts/child/misc/IParentToken.sol | 5 +- .../child/misc/LibTokenTransferOrder.sol | 108 +- contracts/child/misc/Marketplace.sol | 92 +- contracts/child/misc/ParentTokenMock.sol | 20 +- contracts/common/Registry.sol | 328 +++-- contracts/common/lib/BytesLib.sol | 366 ++--- contracts/common/lib/Common.sol | 46 +- contracts/common/lib/Merkle.sol | 49 +- contracts/common/lib/MerklePatriciaProof.sol | 257 ++-- contracts/common/lib/PriorityQueue.sol | 154 +- contracts/common/lib/RLPEncode.sol | 141 +- contracts/common/misc/ContractReceiver.sol | 14 +- contracts/common/misc/DelegateProxy.sol | 59 +- contracts/common/misc/ERCProxy.sol | 4 +- contracts/common/misc/Proxy.sol | 75 +- contracts/common/misc/ProxyStorage.sol | 5 +- contracts/common/mixin/Lockable.sol | 25 +- contracts/common/mixin/RootChainable.sol | 42 +- contracts/common/tokens/DummyERC20.sol | 66 +- .../common/tokens/ERC721PlasmaMintable.sol | 28 +- contracts/common/tokens/MaticWETH.sol | 41 +- contracts/common/tokens/RootERC721.sol | 19 +- contracts/common/tokens/TestToken.sol | 29 +- contracts/common/tokens/WETH.sol | 11 +- contracts/root/IRootChain.sol | 12 +- contracts/root/RootChain.sol | 213 +-- contracts/root/RootChainStorage.sol | 50 +- .../root/depositManager/DepositManager.sol | 352 +++-- .../depositManager/DepositManagerProxy.sol | 23 +- .../depositManager/DepositManagerStorage.sol | 42 +- .../root/depositManager/IDepositManager.sol | 13 +- contracts/root/predicates/ERC20Predicate.sol | 986 +++++++------ contracts/root/predicates/ERC721Predicate.sol | 671 +++++---- contracts/root/predicates/IPredicate.sol | 260 ++-- .../root/predicates/MarketplacePredicate.sol | 728 ++++----- .../predicates/MintableERC721Predicate.sol | 383 ++--- .../predicates/TransferWithSigPredicate.sol | 558 +++---- contracts/root/stateSyncer/StateSender.sol | 72 +- contracts/root/withdrawManager/ExitNFT.sol | 60 +- .../root/withdrawManager/IWithdrawManager.sol | 41 +- .../root/withdrawManager/WithdrawManager.sol | 766 +++++----- .../withdrawManager/WithdrawManagerProxy.sol | 29 +- .../WithdrawManagerStorage.sol | 119 +- contracts/staking/IStakeManager.sol | 88 +- contracts/staking/IValidatorShare.sol | 102 +- contracts/staking/SlashingManager.sol | 116 +- contracts/staking/StakeManager.sol | 1308 ++++++++++------- contracts/staking/StakingInfo.sol | 503 ++++--- contracts/staking/ValidatorShare.sol | 321 ++-- contracts/test/MarketplacePredicateTest.sol | 106 +- contracts/test/StakeManagerTest.sol | 39 +- contracts/test/TestMaticChildERC20.sol | 5 +- 63 files changed, 6211 insertions(+), 4992 deletions(-) diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol index 49c8ef98c..d542e7fd0 100644 --- a/contracts/Migrations.sol +++ b/contracts/Migrations.sol @@ -1,24 +1,23 @@ pragma solidity ^0.5.2; - contract Migrations { - address public owner; - uint public last_completed_migration; + address public owner; + uint256 public last_completed_migration; - modifier restricted() { - if (msg.sender == owner) _; - } + modifier restricted() { + if (msg.sender == owner) _; + } - constructor () public { - owner = msg.sender; - } + constructor() public { + owner = msg.sender; + } - function setCompleted(uint completed) public restricted { - last_completed_migration = completed; - } + function setCompleted(uint256 completed) public restricted { + last_completed_migration = completed; + } - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(last_completed_migration); - } + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } } diff --git a/contracts/child/BaseERC20.sol b/contracts/child/BaseERC20.sol index 6265e86a5..28beaaf7e 100644 --- a/contracts/child/BaseERC20.sol +++ b/contracts/child/BaseERC20.sol @@ -1,79 +1,90 @@ pragma solidity ^0.5.2; -import { ERC20 } from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; +import {ERC20} from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; import "./ChildToken.sol"; - contract BaseERC20 is ChildToken { + event Deposit( + address indexed token, + address indexed from, + uint256 amount, + uint256 input1, + uint256 output1 + ); - event Deposit( - address indexed token, - address indexed from, - uint256 amount, - uint256 input1, - uint256 output1 - ); - - event Withdraw( - address indexed token, - address indexed from, - uint256 amount, - uint256 input1, - uint256 output1 - ); + event Withdraw( + address indexed token, + address indexed from, + uint256 amount, + uint256 input1, + uint256 output1 + ); - event LogTransfer( - address indexed token, - address indexed from, - address indexed to, - uint256 amount, - uint256 input1, - uint256 input2, - uint256 output1, - uint256 output2 - ); + event LogTransfer( + address indexed token, + address indexed from, + address indexed to, + uint256 amount, + uint256 input1, + uint256 input2, + uint256 output1, + uint256 output2 + ); - constructor() public {} + constructor() public {} - function transferWithSig(bytes calldata sig, uint256 amount, bytes32 data, uint256 expiration, address to) external returns (address from) { - require(amount > 0); - require(expiration == 0 || block.number <= expiration, "Signature is expired"); + function transferWithSig( + bytes calldata sig, + uint256 amount, + bytes32 data, + uint256 expiration, + address to + ) external returns (address from) { + require(amount > 0); + require( + expiration == 0 || block.number <= expiration, + "Signature is expired" + ); - bytes32 dataHash = getTokenTransferOrderHash( - msg.sender, - amount, - data, - expiration - ); - require(disabledHashes[dataHash] == false, "Sig deactivated"); - disabledHashes[dataHash] = true; + bytes32 dataHash = getTokenTransferOrderHash( + msg.sender, + amount, + data, + expiration + ); + require(disabledHashes[dataHash] == false, "Sig deactivated"); + disabledHashes[dataHash] = true; - from = ecrecovery(dataHash, sig); - _transferFrom(from, address(uint160(to)), amount); - } + from = ecrecovery(dataHash, sig); + _transferFrom(from, address(uint160(to)), amount); + } - function balanceOf(address account) external view returns (uint256); - function _transfer(address sender, address recipient, uint256 amount) internal; + function balanceOf(address account) external view returns (uint256); + function _transfer(address sender, address recipient, uint256 amount) + internal; - /// @param from Address from where tokens are withdrawn. - /// @param to Address to where tokens are sent. - /// @param value Number of tokens to transfer. - /// @return Returns success of function call. - function _transferFrom(address from, address to, uint256 value) internal returns (bool) { - uint256 input1 = this.balanceOf(from); - uint256 input2 = this.balanceOf(to); - _transfer(from, to, value); - emit LogTransfer( - token, - from, - to, - value, - input1, - input2, - this.balanceOf(from), - this.balanceOf(to) - ); - return true; - } + /// @param from Address from where tokens are withdrawn. + /// @param to Address to where tokens are sent. + /// @param value Number of tokens to transfer. + /// @return Returns success of function call. + function _transferFrom(address from, address to, uint256 value) + internal + returns (bool) + { + uint256 input1 = this.balanceOf(from); + uint256 input2 = this.balanceOf(to); + _transfer(from, to, value); + emit LogTransfer( + token, + from, + to, + value, + input1, + input2, + this.balanceOf(from), + this.balanceOf(to) + ); + return true; + } } diff --git a/contracts/child/ChildChain.sol b/contracts/child/ChildChain.sol index fd913aef2..721a532cf 100644 --- a/contracts/child/ChildChain.sol +++ b/contracts/child/ChildChain.sol @@ -1,153 +1,171 @@ pragma solidity ^0.5.2; -import { Ownable } from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import {Ownable} from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import { StateSyncerVerifier } from "./bor/StateSyncerVerifier.sol"; -import { StateReceiver } from "./bor/StateReceiver.sol"; +import {StateSyncerVerifier} from "./bor/StateSyncerVerifier.sol"; +import {StateReceiver} from "./bor/StateReceiver.sol"; import "./ChildToken.sol"; import "./ChildERC20.sol"; import "./ChildERC721.sol"; - contract ChildChain is Ownable, StateSyncerVerifier, StateReceiver { - - // mapping for (root token => child token) - mapping(address => address) public tokens; - mapping(address => bool) public isERC721; - mapping(uint256 => bool) public deposits; - mapping(uint256 => bool) public withdraws; - - event NewToken( - address indexed rootToken, - address indexed token, - uint8 _decimals - ); - - event TokenDeposited( - address indexed rootToken, - address indexed childToken, - address indexed user, - uint256 amount, - uint256 depositCount - ); - - event TokenWithdrawn( - address indexed rootToken, - address indexed childToken, - address indexed user, - uint256 amount, - uint256 withrawCount - ); - - constructor() public { - //Mapping matic Token - tokens[0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0] = 0x0000000000000000000000000000000000001010; - } - - function onStateReceive( - uint256 /* id */, - bytes calldata data - ) external onlyStateSyncer { - (address user, address rootToken, uint256 amountOrTokenId, uint256 depositId) = abi.decode(data, (address, address, uint256, uint256)); - depositTokens(rootToken, user, amountOrTokenId, depositId); - } - - function addToken( - address _owner, - address _rootToken, - string memory _name, - string memory _symbol, - uint8 _decimals, - bool _isERC721 - ) public onlyOwner returns (address token) { - // check if root token already exists - require(tokens[_rootToken] == address(0x0), "Token already mapped"); - - // create new token contract - if (_isERC721) { - token = address(new ChildERC721(_owner, _rootToken, _name, _symbol)); - isERC721[_rootToken] = true; - } else { - token = address(new ChildERC20(_owner, _rootToken, _name, _symbol, _decimals)); + // mapping for (root token => child token) + mapping(address => address) public tokens; + mapping(address => bool) public isERC721; + mapping(uint256 => bool) public deposits; + mapping(uint256 => bool) public withdraws; + + event NewToken( + address indexed rootToken, + address indexed token, + uint8 _decimals + ); + + event TokenDeposited( + address indexed rootToken, + address indexed childToken, + address indexed user, + uint256 amount, + uint256 depositCount + ); + + event TokenWithdrawn( + address indexed rootToken, + address indexed childToken, + address indexed user, + uint256 amount, + uint256 withrawCount + ); + + constructor() public { + //Mapping matic Token + tokens[0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0] = 0x0000000000000000000000000000000000001010; } - // add mapping with root token - tokens[_rootToken] = token; - - // broadcast new token's event - emit NewToken(_rootToken, token, _decimals); - } - - // for testnet updates remove for mainnet - function mapToken(address rootToken, address token, bool isErc721) public onlyOwner { - tokens[rootToken] = token; - isERC721[rootToken] = isErc721; - } - - function withdrawTokens( - address rootToken, - address user, - uint256 amountOrTokenId, - uint256 withdrawCount - ) public onlyOwner { - // check if withdrawal happens only once - require(withdraws[withdrawCount] == false); - - // set withdrawal flag - withdraws[withdrawCount] = true; - - // retrieve child tokens - address childToken = tokens[rootToken]; - - // check if child token is mapped - require(childToken != address(0x0), "child token is not mapped"); - - ChildToken obj; - - if (isERC721[rootToken]) { - obj = ChildERC721(childToken); - } else { - obj = ChildERC20(childToken); + function onStateReceive( + uint256, /* id */ + bytes calldata data + ) external onlyStateSyncer { + (address user, address rootToken, uint256 amountOrTokenId, uint256 depositId) = abi + .decode(data, (address, address, uint256, uint256)); + depositTokens(rootToken, user, amountOrTokenId, depositId); } - // withdraw tokens - obj.withdraw(amountOrTokenId); - - // Emit TokenWithdrawn event - emit TokenWithdrawn(rootToken, childToken, user, amountOrTokenId, withdrawCount); - } - - function depositTokens( - address rootToken, - address user, - uint256 amountOrTokenId, - uint256 depositId - ) internal { - // check if deposit happens only once - require(deposits[depositId] == false); - - // set deposit flag - deposits[depositId] = true; - - // retrieve child tokens - address childToken = tokens[rootToken]; - // check if child token is mapped - require(childToken != address(0x0)); - - ChildToken obj; + function addToken( + address _owner, + address _rootToken, + string memory _name, + string memory _symbol, + uint8 _decimals, + bool _isERC721 + ) public onlyOwner returns (address token) { + // check if root token already exists + require(tokens[_rootToken] == address(0x0), "Token already mapped"); + + // create new token contract + if (_isERC721) { + token = address( + new ChildERC721(_owner, _rootToken, _name, _symbol) + ); + isERC721[_rootToken] = true; + } else { + token = address( + new ChildERC20(_owner, _rootToken, _name, _symbol, _decimals) + ); + } + + // add mapping with root token + tokens[_rootToken] = token; + + // broadcast new token's event + emit NewToken(_rootToken, token, _decimals); + } - if (isERC721[rootToken]) { - obj = ChildERC721(childToken); - } else { - obj = ChildERC20(childToken); + // for testnet updates remove for mainnet + function mapToken(address rootToken, address token, bool isErc721) + public + onlyOwner + { + tokens[rootToken] = token; + isERC721[rootToken] = isErc721; } - // deposit tokens - obj.deposit(user, amountOrTokenId); + function withdrawTokens( + address rootToken, + address user, + uint256 amountOrTokenId, + uint256 withdrawCount + ) public onlyOwner { + // check if withdrawal happens only once + require(withdraws[withdrawCount] == false); + + // set withdrawal flag + withdraws[withdrawCount] = true; + + // retrieve child tokens + address childToken = tokens[rootToken]; + + // check if child token is mapped + require(childToken != address(0x0), "child token is not mapped"); + + ChildToken obj; + + if (isERC721[rootToken]) { + obj = ChildERC721(childToken); + } else { + obj = ChildERC20(childToken); + } + // withdraw tokens + obj.withdraw(amountOrTokenId); + + // Emit TokenWithdrawn event + emit TokenWithdrawn( + rootToken, + childToken, + user, + amountOrTokenId, + withdrawCount + ); + } - // Emit TokenDeposited event - emit TokenDeposited(rootToken, childToken, user, amountOrTokenId, depositId); - } + function depositTokens( + address rootToken, + address user, + uint256 amountOrTokenId, + uint256 depositId + ) internal { + // check if deposit happens only once + require(deposits[depositId] == false); + + // set deposit flag + deposits[depositId] = true; + + // retrieve child tokens + address childToken = tokens[rootToken]; + + // check if child token is mapped + require(childToken != address(0x0)); + + ChildToken obj; + + if (isERC721[rootToken]) { + obj = ChildERC721(childToken); + } else { + obj = ChildERC20(childToken); + } + + // deposit tokens + obj.deposit(user, amountOrTokenId); + + // Emit TokenDeposited event + emit TokenDeposited( + rootToken, + childToken, + user, + amountOrTokenId, + depositId + ); + } } diff --git a/contracts/child/ChildERC20.sol b/contracts/child/ChildERC20.sol index c3cd3aaa4..76fd1290d 100644 --- a/contracts/child/ChildERC20.sol +++ b/contracts/child/ChildERC20.sol @@ -1,87 +1,92 @@ pragma solidity ^0.5.2; -import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; -import { ERC20 } from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; +import {ERC20} from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; import "./BaseERC20.sol"; import "./misc/IParentToken.sol"; - contract ChildERC20 is BaseERC20, ERC20, ERC20Detailed { + constructor( + address _owner, + address _token, + string memory _name, + string memory _symbol, + uint8 _decimals + ) public ERC20Detailed(_name, _symbol, _decimals) { + require(_token != address(0x0) && _owner != address(0x0)); + parentOwner = _owner; + token = _token; + } - constructor (address _owner, address _token, string memory _name, string memory _symbol, uint8 _decimals) - public - ERC20Detailed(_name, _symbol, _decimals) { - require(_token != address(0x0) && _owner != address(0x0)); - parentOwner = _owner; - token = _token; - } - - function setParent(address _parent) public isParentOwner { - require(_parent != address(0x0)); - parent = _parent; - } + function setParent(address _parent) public isParentOwner { + require(_parent != address(0x0)); + parent = _parent; + } - /** + /** * Deposit tokens * * @param user address for address * @param amount token balance */ - function deposit(address user, uint256 amount) public onlyOwner { - // check for amount and user - require(amount > 0 && user != address(0x0)); + function deposit(address user, uint256 amount) public onlyOwner { + // check for amount and user + require(amount > 0 && user != address(0x0)); - // input balance - uint256 input1 = balanceOf(user); + // input balance + uint256 input1 = balanceOf(user); - // increase balance - _mint(user, amount); + // increase balance + _mint(user, amount); - // deposit events - emit Deposit(token, user, amount, input1, balanceOf(user)); - } + // deposit events + emit Deposit(token, user, amount, input1, balanceOf(user)); + } - /** + /** * Withdraw tokens * * @param amount tokens */ - function withdraw(uint256 amount) public payable { - address user = msg.sender; - // input balance - uint256 input = balanceOf(user); - - // check for amount - require(amount > 0 && input >= amount); - - // decrease balance - _burn(user, amount); - - // withdraw event - emit Withdraw(token, user, amount, input, balanceOf(user)); - } - - /// @dev Function that is called when a user or another contract wants to transfer funds. - /// @param to Address of token receiver. - /// @param value Number of tokens to transfer. - /// @return Returns success of function call. - function transfer(address to, uint256 value) public returns (bool) { - if (parent != address(0x0) && !IParentToken(parent).beforeTransfer(msg.sender, to, value)) { - return false; + function withdraw(uint256 amount) public payable { + address user = msg.sender; + // input balance + uint256 input = balanceOf(user); + + // check for amount + require(amount > 0 && input >= amount); + + // decrease balance + _burn(user, amount); + + // withdraw event + emit Withdraw(token, user, amount, input, balanceOf(user)); + } + + /// @dev Function that is called when a user or another contract wants to transfer funds. + /// @param to Address of token receiver. + /// @param value Number of tokens to transfer. + /// @return Returns success of function call. + function transfer(address to, uint256 value) public returns (bool) { + if ( + parent != address(0x0) && + !IParentToken(parent).beforeTransfer(msg.sender, to, value) + ) { + return false; + } + return _transferFrom(msg.sender, to, value); } - return _transferFrom(msg.sender, to, value); - } - function allowance(address, address) public view returns (uint256) { - revert("Disabled feature"); - } + function allowance(address, address) public view returns (uint256) { + revert("Disabled feature"); + } - function approve(address, uint256) public returns (bool) { - revert("Disabled feature"); - } + function approve(address, uint256) public returns (bool) { + revert("Disabled feature"); + } - function transferFrom(address, address, uint256 ) public returns (bool) { - revert("Disabled feature"); - } + function transferFrom(address, address, uint256) public returns (bool) { + revert("Disabled feature"); + } } diff --git a/contracts/child/ChildERC721.sol b/contracts/child/ChildERC721.sol index 2808724f8..6b1c71e35 100644 --- a/contracts/child/ChildERC721.sol +++ b/contracts/child/ChildERC721.sol @@ -1,122 +1,135 @@ pragma solidity ^0.5.2; -import { ERC721Full } from "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol"; +import { + ERC721Full +} from "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol"; import "./ChildToken.sol"; import "./misc/IParentToken.sol"; - contract ChildERC721 is ChildToken, ERC721Full { + event Deposit(address indexed token, address indexed from, uint256 tokenId); - event Deposit( - address indexed token, - address indexed from, - uint256 tokenId - ); - - event Withdraw( - address indexed token, - address indexed from, - uint256 tokenId - ); - - event LogTransfer( - address indexed token, - address indexed from, - address indexed to, - uint256 tokenId - ); - - constructor (address _owner, address _token, string memory name, string memory symbol) - public - ERC721Full(name, symbol) - { - require(_token != address(0x0) && _owner != address(0x0)); - parentOwner = _owner; - token = _token; - } - - function transferWithSig(bytes calldata sig, uint256 tokenId, bytes32 data, uint256 expiration, address to) external returns (address) { - require(expiration == 0 || block.number <= expiration, "Signature is expired"); - - bytes32 dataHash = getTokenTransferOrderHash( - msg.sender, - tokenId, - data, - expiration + event Withdraw( + address indexed token, + address indexed from, + uint256 tokenId ); - require(disabledHashes[dataHash] == false, "Sig deactivated"); - disabledHashes[dataHash] = true; - - // recover address and send tokens - address from = ecrecovery(dataHash, sig); - _transferFrom(from, to, tokenId); - require( - _checkOnERC721Received(from, to, tokenId, ""), - "_checkOnERC721Received failed" + + event LogTransfer( + address indexed token, + address indexed from, + address indexed to, + uint256 tokenId ); - return from; - } - function setParent(address _parent) public isParentOwner { - require(_parent != address(0x0)); - parent = _parent; - } + constructor( + address _owner, + address _token, + string memory name, + string memory symbol + ) public ERC721Full(name, symbol) { + require(_token != address(0x0) && _owner != address(0x0)); + parentOwner = _owner; + token = _token; + } + + function transferWithSig( + bytes calldata sig, + uint256 tokenId, + bytes32 data, + uint256 expiration, + address to + ) external returns (address) { + require( + expiration == 0 || block.number <= expiration, + "Signature is expired" + ); + + bytes32 dataHash = getTokenTransferOrderHash( + msg.sender, + tokenId, + data, + expiration + ); + require(disabledHashes[dataHash] == false, "Sig deactivated"); + disabledHashes[dataHash] = true; + + // recover address and send tokens + address from = ecrecovery(dataHash, sig); + _transferFrom(from, to, tokenId); + require( + _checkOnERC721Received(from, to, tokenId, ""), + "_checkOnERC721Received failed" + ); + return from; + } + + function setParent(address _parent) public isParentOwner { + require(_parent != address(0x0)); + parent = _parent; + } - function approve(address to, uint256 tokenId) public { - revert("Disabled feature"); - } + function approve(address to, uint256 tokenId) public { + revert("Disabled feature"); + } - function getApproved(uint256 tokenId) public view returns (address operator) { - revert("Disabled feature"); - } + function getApproved(uint256 tokenId) + public + view + returns (address operator) + { + revert("Disabled feature"); + } - function setApprovalForAll(address operator, bool _approved) public { - revert("Disabled feature"); - } + function setApprovalForAll(address operator, bool _approved) public { + revert("Disabled feature"); + } - function isApprovedForAll(address owner, address operator) public view returns (bool){ - revert("Disabled feature"); - } + function isApprovedForAll(address owner, address operator) + public + view + returns (bool) + { + revert("Disabled feature"); + } - /** + /** * @notice Deposit tokens * @param user address for deposit * @param tokenId tokenId to mint to user's account */ - function deposit(address user, uint256 tokenId) public onlyOwner { - require(user != address(0x0)); - _mint(user, tokenId); - emit Deposit(token, user, tokenId); - } + function deposit(address user, uint256 tokenId) public onlyOwner { + require(user != address(0x0)); + _mint(user, tokenId); + emit Deposit(token, user, tokenId); + } - /** + /** * @notice Withdraw tokens * @param tokenId tokenId of the token to be withdrawn */ - function withdraw(uint256 tokenId) public payable { - require(ownerOf(tokenId) == msg.sender); - _burn(msg.sender, tokenId); - emit Withdraw(token, msg.sender, tokenId); - } + function withdraw(uint256 tokenId) public payable { + require(ownerOf(tokenId) == msg.sender); + _burn(msg.sender, tokenId); + emit Withdraw(token, msg.sender, tokenId); + } - /** + /** * @dev Overriding the inherited method so that it emits LogTransfer */ - function transferFrom(address from, address to, uint256 tokenId) public { - if (parent != address(0x0) && !IParentToken(parent).beforeTransfer(msg.sender, to, tokenId)) { - return; + function transferFrom(address from, address to, uint256 tokenId) public { + if ( + parent != address(0x0) && + !IParentToken(parent).beforeTransfer(msg.sender, to, tokenId) + ) { + return; + } + _transferFrom(from, to, tokenId); + } + + function _transferFrom(address from, address to, uint256 tokenId) internal { + super._transferFrom(from, to, tokenId); + emit LogTransfer(token, from, to, tokenId); } - _transferFrom(from, to, tokenId); - } - - function _transferFrom(address from, address to, uint256 tokenId) internal { - super._transferFrom(from, to, tokenId); - emit LogTransfer( - token, - from, - to, - tokenId - ); - } } diff --git a/contracts/child/ChildERC721Mintable.sol b/contracts/child/ChildERC721Mintable.sol index d2b30a5e5..d74eb283b 100644 --- a/contracts/child/ChildERC721Mintable.sol +++ b/contracts/child/ChildERC721Mintable.sol @@ -1,13 +1,22 @@ pragma solidity ^0.5.2; -import { ERC721Mintable } from "openzeppelin-solidity/contracts/token/ERC721/ERC721Mintable.sol"; -import { ERC721MetadataMintable } from "openzeppelin-solidity/contracts/token/ERC721/ERC721MetadataMintable.sol"; +import "openzeppelin-solidity/contracts/token/ERC721/ERC721Mintable.sol"; +import "openzeppelin-solidity/contracts/token/ERC721/ERC721MetadataMintable.sol"; -import { ChildERC721 } from "./ChildERC721.sol"; +import {ChildERC721} from "./ChildERC721.sol"; - -contract ChildERC721Mintable is ChildERC721, ERC721Mintable, ERC721MetadataMintable { - constructor (address rootToken, string memory name, string memory symbol) - ChildERC721(msg.sender /* _owner */, rootToken, name, symbol) - public {} +contract ChildERC721Mintable is + ChildERC721, + ERC721Mintable, + ERC721MetadataMintable +{ + constructor(address rootToken, string memory name, string memory symbol) + public + ChildERC721( + msg.sender, /* _owner */ + rootToken, + name, + symbol + ) + {} } diff --git a/contracts/child/ChildToken.sol b/contracts/child/ChildToken.sol index 519a8978d..d5c50936b 100644 --- a/contracts/child/ChildToken.sol +++ b/contracts/child/ChildToken.sol @@ -5,62 +5,62 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; import "./misc/LibTokenTransferOrder.sol"; - contract ChildToken is Ownable, LibTokenTransferOrder { - using SafeMath for uint256; + using SafeMath for uint256; - // ERC721/ERC20 contract token address on root chain - address public token; - address public parent; - address public parentOwner; + // ERC721/ERC20 contract token address on root chain + address public token; + address public parent; + address public parentOwner; - mapping(bytes32 => bool) public disabledHashes; + mapping(bytes32 => bool) public disabledHashes; - modifier isParentOwner() { - require(msg.sender == parentOwner); - _; - } + modifier isParentOwner() { + require(msg.sender == parentOwner); + _; + } - function deposit(address user, uint256 amountOrTokenId) public; - function withdraw(uint256 amountOrTokenId) public payable; - function setParent(address _parent) public; + function deposit(address user, uint256 amountOrTokenId) public; + function withdraw(uint256 amountOrTokenId) public payable; + function setParent(address _parent) public; - event LogFeeTransfer( - address indexed token, - address indexed from, - address indexed to, - uint256 amount, - uint256 input1, - uint256 input2, - uint256 output1, - uint256 output2 - ); + event LogFeeTransfer( + address indexed token, + address indexed from, + address indexed to, + uint256 amount, + uint256 input1, + uint256 input2, + uint256 output1, + uint256 output2 + ); - function ecrecovery( - bytes32 hash, - bytes memory sig - ) public pure returns (address result) { - bytes32 r; - bytes32 s; - uint8 v; - if (sig.length != 65) { - return address(0x0); - } - assembly { - r := mload(add(sig, 32)) - s := mload(add(sig, 64)) - v := and(mload(add(sig, 65)), 255) - } - // https://github.com/ethereum/go-ethereum/issues/2053 - if (v < 27) { - v += 27; - } - if (v != 27 && v != 28) { - return address(0x0); + function ecrecovery(bytes32 hash, bytes memory sig) + public + pure + returns (address result) + { + bytes32 r; + bytes32 s; + uint8 v; + if (sig.length != 65) { + return address(0x0); + } + assembly { + r := mload(add(sig, 32)) + s := mload(add(sig, 64)) + v := and(mload(add(sig, 65)), 255) + } + // https://github.com/ethereum/go-ethereum/issues/2053 + if (v < 27) { + v += 27; + } + if (v != 27 && v != 28) { + return address(0x0); + } + // get address out of hash and signature + result = ecrecover(hash, v, r, s); + // ecrecover returns zero on error + require(result != address(0x0), "Error in ecrecover"); } - // get address out of hash and signature - result = ecrecover(hash, v, r, s); - // ecrecover returns zero on error - require(result != address(0x0), "Error in ecrecover"); - } } diff --git a/contracts/child/MaticChildERC20.sol b/contracts/child/MaticChildERC20.sol index ce00c5432..8364b254d 100644 --- a/contracts/child/MaticChildERC20.sol +++ b/contracts/child/MaticChildERC20.sol @@ -2,98 +2,103 @@ pragma solidity ^0.5.11; import "./BaseERC20.sol"; - contract MaticChildERC20 is BaseERC20 { + event Transfer(address indexed from, address indexed to, uint256 value); + + uint256 public currentSupply = 0; + uint8 private constant DECIMALS = 18; + bool isInitialized; + + constructor() public {} + + function initialize(address _childChain, address _token) public { + // Todo: once BorValidator(@0x1000) contract added uncomment me + // require(msg.sender == address(0x1000)); + require(!isInitialized, "The contract is already initialized"); + isInitialized = true; + token = _token; + _transferOwnership(_childChain); + } + + function setParent(address) public { + revert("Disabled feature"); + } + + function deposit(address user, uint256 amount) public onlyOwner { + // check for amount and user + require( + amount > 0 && user != address(0x0), + "Insufficient amount or invalid user" + ); + + // input balance + uint256 input1 = balanceOf(user); + + // transfer amount to user + address payable _user = address(uint160(user)); + _user.transfer(amount); + + currentSupply = currentSupply.add(amount); + + // deposit events + emit Deposit(token, user, amount, input1, balanceOf(user)); + } + + function withdraw(uint256 amount) public payable { + address user = msg.sender; + // input balance + uint256 input = balanceOf(user); - event Transfer(address indexed from, address indexed to, uint256 value); - - uint256 public currentSupply = 0; - uint8 constant private DECIMALS = 18; - bool isInitialized; - - constructor() public {} - - function initialize(address _childChain, address _token) - public { - // Todo: once BorValidator(@0x1000) contract added uncomment me - // require(msg.sender == address(0x1000)); - require(!isInitialized, "The contract is already initialized"); - isInitialized = true; - token = _token; - _transferOwnership(_childChain); - } - - function setParent(address ) public { - revert("Disabled feature"); - } - - function deposit(address user, uint256 amount) public onlyOwner { - // check for amount and user - require(amount > 0 && user != address(0x0), "Insufficient amount or invalid user"); - - // input balance - uint256 input1 = balanceOf(user); - - // transfer amount to user - address payable _user = address(uint160(user)); - _user.transfer(amount); - - currentSupply = currentSupply.add(amount); - - // deposit events - emit Deposit(token, user, amount, input1, balanceOf(user)); - } - - function withdraw(uint256 amount) public payable { - address user = msg.sender; - // input balance - uint256 input = balanceOf(user); - - currentSupply = currentSupply.sub(amount); - // check for amount - require(amount > 0 && input >= amount && msg.value == amount, "Insufficient amount"); - - // withdraw event - emit Withdraw(token, user, amount, input, balanceOf(user)); - } - - function name() public pure returns (string memory) { - return "Matic Token"; - } - - function symbol() public pure returns (string memory) { - return "MATIC"; - } - - function decimals() public pure returns (uint8) { - return DECIMALS; - } - - function totalSupply() public view returns (uint256) { - return 10000000000 * 10 ** uint256(DECIMALS); - } - - function balanceOf(address account) public view returns (uint256) { - return account.balance; - } - - /// @dev Function that is called when a user or another contract wants to transfer funds. - /// @param to Address of token receiver. - /// @param value Number of tokens to transfer. - /// @return Returns success of function call. - function transfer(address to, uint256 value) public payable returns (bool) { - if (msg.value != value) { - return false; + currentSupply = currentSupply.sub(amount); + // check for amount + require( + amount > 0 && input >= amount && msg.value == amount, + "Insufficient amount" + ); + + // withdraw event + emit Withdraw(token, user, amount, input, balanceOf(user)); + } + + function name() public pure returns (string memory) { + return "Matic Token"; + } + + function symbol() public pure returns (string memory) { + return "MATIC"; + } + + function decimals() public pure returns (uint8) { + return DECIMALS; } - return _transferFrom(msg.sender, to, value); - } - /** + function totalSupply() public view returns (uint256) { + return 10000000000 * 10**uint256(DECIMALS); + } + + function balanceOf(address account) public view returns (uint256) { + return account.balance; + } + + /// @dev Function that is called when a user or another contract wants to transfer funds. + /// @param to Address of token receiver. + /// @param value Number of tokens to transfer. + /// @return Returns success of function call. + function transfer(address to, uint256 value) public payable returns (bool) { + if (msg.value != value) { + return false; + } + return _transferFrom(msg.sender, to, value); + } + + /** * @dev _transfer is invoked by _transferFrom method that is inherited from BaseERC20. * This enables us to transfer MaticEth between users while keeping the interface same as that of an ERC20 Token. */ - function _transfer(address sender, address recipient, uint256 amount) internal { - address(uint160(recipient)).transfer(amount); - emit Transfer(sender, recipient, amount); - } + function _transfer(address sender, address recipient, uint256 amount) + internal + { + address(uint160(recipient)).transfer(amount); + emit Transfer(sender, recipient, amount); + } } diff --git a/contracts/child/bor/StateReceiver.sol b/contracts/child/bor/StateReceiver.sol index 4fde23014..e77e44232 100644 --- a/contracts/child/bor/StateReceiver.sol +++ b/contracts/child/bor/StateReceiver.sol @@ -2,5 +2,5 @@ pragma solidity ^0.5.2; // StateReceiver represents interface to receive state interface StateReceiver { - function onStateReceive(uint256 id, bytes calldata data) external; + function onStateReceive(uint256 id, bytes calldata data) external; } diff --git a/contracts/child/bor/StateSyncerVerifier.sol b/contracts/child/bor/StateSyncerVerifier.sol index 6c4cf5f50..afab33d21 100644 --- a/contracts/child/bor/StateSyncerVerifier.sol +++ b/contracts/child/bor/StateSyncerVerifier.sol @@ -1,45 +1,50 @@ pragma solidity ^0.5.2; -import { Ownable } from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; - +import {Ownable} from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract StateSyncerVerifier is Ownable { - address public stateSyncer; + address public stateSyncer; - event StateSyncerAddressChanged( - address indexed previousAddress, - address indexed newAddress - ); + event StateSyncerAddressChanged( + address indexed previousAddress, + address indexed newAddress + ); - /** + /** * @dev Throws if called by any account other than state syncer */ - modifier onlyStateSyncer() { - require(isOnlyStateSyncerContract(), "State syncer: caller is not the state syncer contract"); - _; - } - - // initial setup - constructor () public { - // default state syncer contract - stateSyncer = 0x0000000000000000000000000000000000001001; - - // emit event for first change - emit StateSyncerAddressChanged(address(0), stateSyncer); - } - - /** + modifier onlyStateSyncer() { + require( + isOnlyStateSyncerContract(), + "State syncer: caller is not the state syncer contract" + ); + _; + } + + // initial setup + constructor() public { + // default state syncer contract + stateSyncer = 0x0000000000000000000000000000000000001001; + + // emit event for first change + emit StateSyncerAddressChanged(address(0), stateSyncer); + } + + /** * @dev Returns true if the caller is the state syncer contract * TODO: replace onlyOwner ownership with 0x1000 for validator majority */ - function isOnlyStateSyncerContract() public view returns (bool) { - return msg.sender == stateSyncer; - } - - // change state syncer address - function changeStateSyncerAddress(address newAddress) public onlyOwner { - require(newAddress != address(0), "State syncer: new state syncer address is the zero address"); - emit StateSyncerAddressChanged(stateSyncer, newAddress); - stateSyncer = newAddress; - } + function isOnlyStateSyncerContract() public view returns (bool) { + return msg.sender == stateSyncer; + } + + // change state syncer address + function changeStateSyncerAddress(address newAddress) public onlyOwner { + require( + newAddress != address(0), + "State syncer: new state syncer address is the zero address" + ); + emit StateSyncerAddressChanged(stateSyncer, newAddress); + stateSyncer = newAddress; + } } diff --git a/contracts/child/misc/EIP712.sol b/contracts/child/misc/EIP712.sol index aa528c0e2..3777704da 100644 --- a/contracts/child/misc/EIP712.sol +++ b/contracts/child/misc/EIP712.sol @@ -1,49 +1,59 @@ pragma solidity ^0.5.2; -import { ChainIdMixin } from "../../common/mixin/ChainIdMixin.sol"; - +import {ChainIdMixin} from "../../common/mixin/ChainIdMixin.sol"; contract LibEIP712Domain is ChainIdMixin { - string constant internal EIP712_DOMAIN_SCHEMA = "EIP712Domain(string name,string version,uint256 chainId,address contract)"; - bytes32 constant public EIP712_DOMAIN_SCHEMA_HASH = keccak256(abi.encodePacked(EIP712_DOMAIN_SCHEMA)); - - string constant internal EIP712_DOMAIN_NAME = "Matic Network"; - string constant internal EIP712_DOMAIN_VERSION = "1"; - uint256 constant internal EIP712_DOMAIN_CHAINID = CHAINID; - - bytes32 public EIP712_DOMAIN_HASH; - - constructor () public { - EIP712_DOMAIN_HASH = keccak256(abi.encode( - EIP712_DOMAIN_SCHEMA_HASH, - keccak256(bytes(EIP712_DOMAIN_NAME)), - keccak256(bytes(EIP712_DOMAIN_VERSION)), - EIP712_DOMAIN_CHAINID, - address(this) - )); - } - - function hashEIP712Message(bytes32 hashStruct) internal view returns (bytes32 result) { - bytes32 domainHash = EIP712_DOMAIN_HASH; - - // Assembly for more efficient computing: - // keccak256(abi.encode( - // EIP191_HEADER, - // domainHash, - // hashStruct - // )); - - assembly { - // Load free memory pointer - let memPtr := mload(64) - - mstore(memPtr, 0x1901000000000000000000000000000000000000000000000000000000000000) // EIP191 header - mstore(add(memPtr, 2), domainHash) // EIP712 domain hash - mstore(add(memPtr, 34), hashStruct) // Hash of struct - - // Compute hash - result := keccak256(memPtr, 66) + string internal constant EIP712_DOMAIN_SCHEMA = "EIP712Domain(string name,string version,uint256 chainId,address contract)"; + bytes32 public constant EIP712_DOMAIN_SCHEMA_HASH = keccak256( + abi.encodePacked(EIP712_DOMAIN_SCHEMA) + ); + + string internal constant EIP712_DOMAIN_NAME = "Matic Network"; + string internal constant EIP712_DOMAIN_VERSION = "1"; + uint256 internal constant EIP712_DOMAIN_CHAINID = CHAINID; + + bytes32 public EIP712_DOMAIN_HASH; + + constructor() public { + EIP712_DOMAIN_HASH = keccak256( + abi.encode( + EIP712_DOMAIN_SCHEMA_HASH, + keccak256(bytes(EIP712_DOMAIN_NAME)), + keccak256(bytes(EIP712_DOMAIN_VERSION)), + EIP712_DOMAIN_CHAINID, + address(this) + ) + ); + } + + function hashEIP712Message(bytes32 hashStruct) + internal + view + returns (bytes32 result) + { + bytes32 domainHash = EIP712_DOMAIN_HASH; + + // Assembly for more efficient computing: + // keccak256(abi.encode( + // EIP191_HEADER, + // domainHash, + // hashStruct + // )); + + assembly { + // Load free memory pointer + let memPtr := mload(64) + + mstore( + memPtr, + 0x1901000000000000000000000000000000000000000000000000000000000000 + ) // EIP191 header + mstore(add(memPtr, 2), domainHash) // EIP712 domain hash + mstore(add(memPtr, 34), hashStruct) // Hash of struct + + // Compute hash + result := keccak256(memPtr, 66) + } + return result; } - return result; - } } diff --git a/contracts/child/misc/IParentToken.sol b/contracts/child/misc/IParentToken.sol index d2c9b9102..1032a530f 100644 --- a/contracts/child/misc/IParentToken.sol +++ b/contracts/child/misc/IParentToken.sol @@ -1,7 +1,8 @@ pragma solidity ^0.5.2; //interface for parent contract of any child token - interface IParentToken { - function beforeTransfer(address sender, address to, uint256 value) external returns(bool); + function beforeTransfer(address sender, address to, uint256 value) + external + returns (bool); } diff --git a/contracts/child/misc/LibTokenTransferOrder.sol b/contracts/child/misc/LibTokenTransferOrder.sol index c4868e705..fb589f8fc 100644 --- a/contracts/child/misc/LibTokenTransferOrder.sol +++ b/contracts/child/misc/LibTokenTransferOrder.sol @@ -1,56 +1,64 @@ pragma solidity ^0.5.2; -import { LibEIP712Domain } from "./EIP712.sol"; - +import {LibEIP712Domain} from "./EIP712.sol"; contract LibTokenTransferOrder is LibEIP712Domain { - string constant internal EIP712_TOKEN_TRANSFER_ORDER_SCHEMA = "TokenTransferOrder(address spender,uint256 tokenIdOrAmount,bytes32 data,uint256 expiration)"; - bytes32 constant public EIP712_TOKEN_TRANSFER_ORDER_SCHEMA_HASH = keccak256(abi.encodePacked(EIP712_TOKEN_TRANSFER_ORDER_SCHEMA)); - - struct TokenTransferOrder { - address spender; - uint256 tokenIdOrAmount; - bytes32 data; - uint256 expiration; - } - - function getTokenTransferOrderHash(address spender, uint256 tokenIdOrAmount, bytes32 data, uint256 expiration) - public - view - returns (bytes32 orderHash) - { - orderHash = hashEIP712Message(hashTokenTransferOrder(spender, tokenIdOrAmount, data, expiration)); - } - - function hashTokenTransferOrder(address spender, uint256 tokenIdOrAmount, bytes32 data, uint256 expiration) - internal - pure - returns (bytes32 result) - { - bytes32 schemaHash = EIP712_TOKEN_TRANSFER_ORDER_SCHEMA_HASH; - - // Assembly for more efficiently computing: - // return keccak256(abi.encode( - // schemaHash, - // spender, - // tokenIdOrAmount, - // data, - // expiration - // )); - - assembly { - // Load free memory pointer - let memPtr := mload(64) - - mstore(memPtr, schemaHash) // hash of schema - mstore(add(memPtr, 32), and(spender, 0xffffffffffffffffffffffffffffffffffffffff)) // spender - mstore(add(memPtr, 64), tokenIdOrAmount) // tokenIdOrAmount - mstore(add(memPtr, 96), data) // hash of data - mstore(add(memPtr, 128), expiration) // expiration - - // Compute hash - result := keccak256(memPtr, 160) + string internal constant EIP712_TOKEN_TRANSFER_ORDER_SCHEMA = "TokenTransferOrder(address spender,uint256 tokenIdOrAmount,bytes32 data,uint256 expiration)"; + bytes32 public constant EIP712_TOKEN_TRANSFER_ORDER_SCHEMA_HASH = keccak256( + abi.encodePacked(EIP712_TOKEN_TRANSFER_ORDER_SCHEMA) + ); + + struct TokenTransferOrder { + address spender; + uint256 tokenIdOrAmount; + bytes32 data; + uint256 expiration; + } + + function getTokenTransferOrderHash( + address spender, + uint256 tokenIdOrAmount, + bytes32 data, + uint256 expiration + ) public view returns (bytes32 orderHash) { + orderHash = hashEIP712Message( + hashTokenTransferOrder(spender, tokenIdOrAmount, data, expiration) + ); + } + + function hashTokenTransferOrder( + address spender, + uint256 tokenIdOrAmount, + bytes32 data, + uint256 expiration + ) internal pure returns (bytes32 result) { + bytes32 schemaHash = EIP712_TOKEN_TRANSFER_ORDER_SCHEMA_HASH; + + // Assembly for more efficiently computing: + // return keccak256(abi.encode( + // schemaHash, + // spender, + // tokenIdOrAmount, + // data, + // expiration + // )); + + assembly { + // Load free memory pointer + let memPtr := mload(64) + + mstore(memPtr, schemaHash) // hash of schema + mstore( + add(memPtr, 32), + and(spender, 0xffffffffffffffffffffffffffffffffffffffff) + ) // spender + mstore(add(memPtr, 64), tokenIdOrAmount) // tokenIdOrAmount + mstore(add(memPtr, 96), data) // hash of data + mstore(add(memPtr, 128), expiration) // expiration + + // Compute hash + result := keccak256(memPtr, 160) + } + return result; } - return result; - } } diff --git a/contracts/child/misc/Marketplace.sol b/contracts/child/misc/Marketplace.sol index 7660c8f16..e27c5e9a0 100644 --- a/contracts/child/misc/Marketplace.sol +++ b/contracts/child/misc/Marketplace.sol @@ -1,48 +1,66 @@ pragma solidity ^0.5.2; interface MarketplaceToken { - function transferWithSig(bytes calldata sig, uint256 tokenIdOrAmount, bytes32 data, uint256 expiration, address to) external returns (address); + function transferWithSig( + bytes calldata sig, + uint256 tokenIdOrAmount, + bytes32 data, + uint256 expiration, + address to + ) external returns (address); } - contract Marketplace { - struct Order { - address token; - bytes sig; - uint256 tokenIdOrAmount; - } + struct Order { + address token; + bytes sig; + uint256 tokenIdOrAmount; + } - function executeOrder( - bytes memory data1, - bytes memory data2, - bytes32 orderId, - uint256 expiration, - address taker - ) public { - Order memory order1 = decode(data1); - Order memory order2 = decode(data2); + function executeOrder( + bytes memory data1, + bytes memory data2, + bytes32 orderId, + uint256 expiration, + address taker + ) public { + Order memory order1 = decode(data1); + Order memory order2 = decode(data2); - // Transferring order1.token tokens from tradeParticipant1 to address2 - address tradeParticipant1 = MarketplaceToken(order1.token).transferWithSig( - order1.sig, - order1.tokenIdOrAmount, - keccak256(abi.encodePacked(orderId, order2.token, order2.tokenIdOrAmount)), - expiration, - taker - ); + // Transferring order1.token tokens from tradeParticipant1 to address2 + address tradeParticipant1 = MarketplaceToken(order1.token) + .transferWithSig( + order1.sig, + order1.tokenIdOrAmount, + keccak256( + abi.encodePacked(orderId, order2.token, order2.tokenIdOrAmount) + ), + expiration, + taker + ); - // Transferring token2 from tradeParticipant2 to tradeParticipant1 - address tradeParticipant2 = MarketplaceToken(order2.token).transferWithSig( - order2.sig, - order2.tokenIdOrAmount, - keccak256(abi.encodePacked(orderId, order1.token, order1.tokenIdOrAmount)), - expiration, - tradeParticipant1 - ); - require(taker == tradeParticipant2, "Orders are not complimentary"); - } + // Transferring token2 from tradeParticipant2 to tradeParticipant1 + address tradeParticipant2 = MarketplaceToken(order2.token) + .transferWithSig( + order2.sig, + order2.tokenIdOrAmount, + keccak256( + abi.encodePacked(orderId, order1.token, order1.tokenIdOrAmount) + ), + expiration, + tradeParticipant1 + ); + require(taker == tradeParticipant2, "Orders are not complimentary"); + } - function decode(bytes memory data) internal pure returns(Order memory order) { - (order.token, order.sig, order.tokenIdOrAmount) = abi.decode(data, (address, bytes, uint256)); - } + function decode(bytes memory data) + internal + pure + returns (Order memory order) + { + (order.token, order.sig, order.tokenIdOrAmount) = abi.decode( + data, + (address, bytes, uint256) + ); + } } diff --git a/contracts/child/misc/ParentTokenMock.sol b/contracts/child/misc/ParentTokenMock.sol index 73781f5a0..ed0a83759 100644 --- a/contracts/child/misc/ParentTokenMock.sol +++ b/contracts/child/misc/ParentTokenMock.sol @@ -5,15 +5,17 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; import "./IParentToken.sol"; // demo token parent contract - contract ParentTokenMock is IParentToken, Ownable { - mapping (address => bool) isAllowed; - function beforeTransfer(address sender, address to, uint256 value) external returns(bool) { - return isAllowed[sender]; - } + mapping(address => bool) isAllowed; + function beforeTransfer(address sender, address to, uint256 value) + external + returns (bool) + { + return isAllowed[sender]; + } - function updatePermission(address user) public onlyOwner { - require(user != address(0x0)); - isAllowed[user] = !isAllowed[user]; - } + function updatePermission(address user) public onlyOwner { + require(user != address(0x0)); + isAllowed[user] = !isAllowed[user]; + } } diff --git a/contracts/common/Registry.sol b/contracts/common/Registry.sol index 43f1392dd..d76d86047 100644 --- a/contracts/common/Registry.sol +++ b/contracts/common/Registry.sol @@ -1,160 +1,188 @@ pragma solidity ^0.5.2; -import { Ownable } from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import { IWithdrawManager } from "../root/withdrawManager/IWithdrawManager.sol"; - +import {Ownable} from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import {IWithdrawManager} from "../root/withdrawManager/IWithdrawManager.sol"; contract Registry is Ownable { - // @todo hardcode constants - bytes32 constant private WETH_TOKEN = keccak256("wethToken"); - bytes32 constant private DEPOSIT_MANAGER = keccak256("depositManager"); - bytes32 constant private STAKE_MANAGER = keccak256("stakeManager"); - bytes32 constant private DELEGATION_MANAGER = keccak256("delegationManager"); - bytes32 constant private WITHDRAW_MANAGER = keccak256("withdrawManager"); - bytes32 constant private CHILD_CHAIN = keccak256("childChain"); - bytes32 constant private STATE_SENDER = keccak256("stateSender"); - bytes32 constant private SLASHING_MANAGER = keccak256("slashingManager"); - - address public erc20Predicate; - address public erc721Predicate; - - mapping(bytes32 => address) contractMap; - mapping(address => address) public rootToChildToken; - mapping(address => address) public childToRootToken; - // @todo we can think of one function from the registry which returns both (childToken,isERC721) if we are using it frequently together. - mapping(address => bool) public proofValidatorContracts; - mapping(address => bool) public isERC721; - - enum Type { Invalid, ERC20, ERC721, Custom } - struct Predicate { - Type _type; - } - mapping(address => Predicate) public predicates; - - event TokenMapped(address indexed rootToken, address indexed childToken); - event ProofValidatorAdded(address indexed validator, address indexed from); - event ProofValidatorRemoved(address indexed validator, address indexed from); - event PredicateAdded(address indexed predicate, address indexed from); - event PredicateRemoved(address indexed predicate, address indexed from); - event ContractMapUpdated( - bytes32 indexed key, - address indexed previousContract, - address indexed newContract - ); - - function updateContractMap(bytes32 _key, address _address) - external - onlyOwner - { - emit ContractMapUpdated(_key, contractMap[_key], _address); - contractMap[_key] = _address; - } - - /** + // @todo hardcode constants + bytes32 private constant WETH_TOKEN = keccak256("wethToken"); + bytes32 private constant DEPOSIT_MANAGER = keccak256("depositManager"); + bytes32 private constant STAKE_MANAGER = keccak256("stakeManager"); + bytes32 private constant DELEGATION_MANAGER = keccak256( + "delegationManager" + ); + bytes32 private constant WITHDRAW_MANAGER = keccak256("withdrawManager"); + bytes32 private constant CHILD_CHAIN = keccak256("childChain"); + bytes32 private constant STATE_SENDER = keccak256("stateSender"); + bytes32 private constant SLASHING_MANAGER = keccak256("slashingManager"); + + address public erc20Predicate; + address public erc721Predicate; + + mapping(bytes32 => address) contractMap; + mapping(address => address) public rootToChildToken; + mapping(address => address) public childToRootToken; + // @todo we can think of one function from the registry which returns both (childToken,isERC721) if we are using it frequently together. + mapping(address => bool) public proofValidatorContracts; + mapping(address => bool) public isERC721; + + enum Type {Invalid, ERC20, ERC721, Custom} + struct Predicate { + Type _type; + } + mapping(address => Predicate) public predicates; + + event TokenMapped(address indexed rootToken, address indexed childToken); + event ProofValidatorAdded(address indexed validator, address indexed from); + event ProofValidatorRemoved( + address indexed validator, + address indexed from + ); + event PredicateAdded(address indexed predicate, address indexed from); + event PredicateRemoved(address indexed predicate, address indexed from); + event ContractMapUpdated( + bytes32 indexed key, + address indexed previousContract, + address indexed newContract + ); + + function updateContractMap(bytes32 _key, address _address) + external + onlyOwner + { + emit ContractMapUpdated(_key, contractMap[_key], _address); + contractMap[_key] = _address; + } + + /** * @dev Map root token to child token * @param _rootToken Token address on the root chain * @param _childToken Token address on the child chain * @param _isERC721 Is the token being mapped ERC721 */ - function mapToken(address _rootToken, address _childToken, bool _isERC721) - external - onlyOwner - { - require( - _rootToken != address(0x0) && _childToken != address(0x0), - "INVALID_TOKEN_ADDRESS" - ); - rootToChildToken[_rootToken] = _childToken; - childToRootToken[_childToken] = _rootToken; - isERC721[_rootToken] = _isERC721; - IWithdrawManager(contractMap[WITHDRAW_MANAGER]).createExitQueue(_rootToken); - emit TokenMapped(_rootToken, _childToken); - } - - function addProofValidator(address _validator) public onlyOwner { - require(_validator != address(0) && proofValidatorContracts[_validator] != true); - emit ProofValidatorAdded(_validator, msg.sender); - proofValidatorContracts[_validator] = true; - } - - function addErc20Predicate(address predicate) public onlyOwner { - require(predicate != address(0x0), "Can not add null address as predicate"); - erc20Predicate = predicate; - addPredicate(predicate, Type.ERC20); - } - - function addErc721Predicate(address predicate) public onlyOwner { - erc721Predicate = predicate; - addPredicate(predicate, Type.ERC721); - } - - function addPredicate(address predicate, Type _type) public onlyOwner - { - require(predicates[predicate]._type == Type.Invalid, "Predicate already added"); - predicates[predicate]._type = _type; - emit PredicateAdded(predicate, msg.sender); - } - - function removePredicate(address predicate) public onlyOwner - { - require(predicates[predicate]._type != Type.Invalid, "Predicate does not exist"); - delete predicates[predicate]; - emit PredicateRemoved(predicate, msg.sender); - } - - function removeProofValidator(address _validator) public onlyOwner { - require(proofValidatorContracts[_validator] == true); - emit ProofValidatorRemoved(_validator, msg.sender); - delete proofValidatorContracts[_validator]; - } - - function getDelegationManagerAddress() public view returns(address) { - return contractMap[DELEGATION_MANAGER]; - } - - function getWethTokenAddress() public view returns(address) { - return contractMap[WETH_TOKEN]; - } - - function getDepositManagerAddress() public view returns(address) { - return contractMap[DEPOSIT_MANAGER]; - } - - function getStakeManagerAddress() public view returns(address) { - return contractMap[STAKE_MANAGER]; - } - - function getSlashingManagerAddress() public view returns(address) { - return contractMap[SLASHING_MANAGER]; - } - - function getWithdrawManagerAddress() public view returns(address) { - return contractMap[WITHDRAW_MANAGER]; - } - - function getChildChainAndStateSender() public view returns(address, address) { - return (contractMap[CHILD_CHAIN], contractMap[STATE_SENDER]); - } - - function isTokenMapped(address _token) public view returns (bool) { - return rootToChildToken[_token] != address(0x0); - } - - function isTokenMappedAndIsErc721(address _token) public view returns (bool) { - require(isTokenMapped(_token), "TOKEN_NOT_MAPPED"); - return isERC721[_token]; - } - - function isTokenMappedAndGetPredicate(address _token) public view returns (address) { - if (isTokenMappedAndIsErc721(_token)) { - return erc721Predicate; - } - return erc20Predicate; - } - - function isChildTokenErc721(address childToken) public view returns(bool) { - address rootToken = childToRootToken[childToken]; - require(rootToken != address(0x0), "Child token is not mapped"); - return isERC721[rootToken]; - } + function mapToken(address _rootToken, address _childToken, bool _isERC721) + external + onlyOwner + { + require( + _rootToken != address(0x0) && _childToken != address(0x0), + "INVALID_TOKEN_ADDRESS" + ); + rootToChildToken[_rootToken] = _childToken; + childToRootToken[_childToken] = _rootToken; + isERC721[_rootToken] = _isERC721; + IWithdrawManager(contractMap[WITHDRAW_MANAGER]).createExitQueue( + _rootToken + ); + emit TokenMapped(_rootToken, _childToken); + } + + function addProofValidator(address _validator) public onlyOwner { + require( + _validator != address(0) && + proofValidatorContracts[_validator] != true + ); + emit ProofValidatorAdded(_validator, msg.sender); + proofValidatorContracts[_validator] = true; + } + + function addErc20Predicate(address predicate) public onlyOwner { + require( + predicate != address(0x0), + "Can not add null address as predicate" + ); + erc20Predicate = predicate; + addPredicate(predicate, Type.ERC20); + } + + function addErc721Predicate(address predicate) public onlyOwner { + erc721Predicate = predicate; + addPredicate(predicate, Type.ERC721); + } + + function addPredicate(address predicate, Type _type) public onlyOwner { + require( + predicates[predicate]._type == Type.Invalid, + "Predicate already added" + ); + predicates[predicate]._type = _type; + emit PredicateAdded(predicate, msg.sender); + } + + function removePredicate(address predicate) public onlyOwner { + require( + predicates[predicate]._type != Type.Invalid, + "Predicate does not exist" + ); + delete predicates[predicate]; + emit PredicateRemoved(predicate, msg.sender); + } + + function removeProofValidator(address _validator) public onlyOwner { + require(proofValidatorContracts[_validator] == true); + emit ProofValidatorRemoved(_validator, msg.sender); + delete proofValidatorContracts[_validator]; + } + + function getDelegationManagerAddress() public view returns (address) { + return contractMap[DELEGATION_MANAGER]; + } + + function getWethTokenAddress() public view returns (address) { + return contractMap[WETH_TOKEN]; + } + + function getDepositManagerAddress() public view returns (address) { + return contractMap[DEPOSIT_MANAGER]; + } + + function getStakeManagerAddress() public view returns (address) { + return contractMap[STAKE_MANAGER]; + } + + function getSlashingManagerAddress() public view returns (address) { + return contractMap[SLASHING_MANAGER]; + } + + function getWithdrawManagerAddress() public view returns (address) { + return contractMap[WITHDRAW_MANAGER]; + } + + function getChildChainAndStateSender() + public + view + returns (address, address) + { + return (contractMap[CHILD_CHAIN], contractMap[STATE_SENDER]); + } + + function isTokenMapped(address _token) public view returns (bool) { + return rootToChildToken[_token] != address(0x0); + } + + function isTokenMappedAndIsErc721(address _token) + public + view + returns (bool) + { + require(isTokenMapped(_token), "TOKEN_NOT_MAPPED"); + return isERC721[_token]; + } + + function isTokenMappedAndGetPredicate(address _token) + public + view + returns (address) + { + if (isTokenMappedAndIsErc721(_token)) { + return erc721Predicate; + } + return erc20Predicate; + } + + function isChildTokenErc721(address childToken) public view returns (bool) { + address rootToken = childToRootToken[childToken]; + require(rootToken != address(0x0), "Child token is not mapped"); + return isERC721[rootToken]; + } } diff --git a/contracts/common/lib/BytesLib.sol b/contracts/common/lib/BytesLib.sol index 864ecd555..5f8118156 100644 --- a/contracts/common/lib/BytesLib.sol +++ b/contracts/common/lib/BytesLib.sol @@ -3,187 +3,213 @@ pragma solidity ^0.5.2; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; library BytesLib { - function concat( - bytes memory _preBytes, - bytes memory _postBytes - ) internal pure returns (bytes memory) { - bytes memory tempBytes; - assembly { - // Get a location of some free memory and store it in tempBytes as - // Solidity does for memory variables. - tempBytes := mload(0x40) - - // Store the length of the first bytes array at the beginning of - // the memory for tempBytes. - let length := mload(_preBytes) - mstore(tempBytes, length) - - // Maintain a memory counter for the current write location in the - // temp bytes array by adding the 32 bytes for the array length to - // the starting location. - let mc := add(tempBytes, 0x20) - // Stop copying when the memory counter reaches the length of the - // first bytes array. - let end := add(mc, length) - - for { - // Initialize a copy counter to the start of the _preBytes data, - // 32 bytes into its memory. - let cc := add(_preBytes, 0x20) - } lt(mc, end) { - // Increase both counters by 32 bytes each iteration. - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - // Write the _preBytes data into the tempBytes memory 32 bytes - // at a time. - mstore(mc, mload(cc)) - } - - // Add the length of _postBytes to the current length of tempBytes - // and store it as the new length in the first 32 bytes of the - // tempBytes memory. - length := mload(_postBytes) - mstore(tempBytes, add(length, mload(tempBytes))) - - // Move the memory counter back from a multiple of 0x20 to the - // actual end of the _preBytes data. - mc := end - // Stop copying when the memory counter reaches the new combined - // length of the arrays. - end := add(mc, length) - - for { - let cc := add(_postBytes, 0x20) - } lt(mc, end) { - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } - - // Update the free-memory pointer by padding our last write location - // to 32 bytes: add 31 bytes to the end of tempBytes to move to the - // next 32 byte block, then round down to the nearest multiple of - // 32. If the sum of the length of the two arrays is zero then add - // one before rounding down to leave a blank 32 bytes (the length block with 0). - mstore(0x40, and( - add(add(end, iszero(add(length, mload(_preBytes)))), 31), - not(31) // Round down to the nearest 32 bytes. - )) + function concat(bytes memory _preBytes, bytes memory _postBytes) + internal + pure + returns (bytes memory) + { + bytes memory tempBytes; + assembly { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // Store the length of the first bytes array at the beginning of + // the memory for tempBytes. + let length := mload(_preBytes) + mstore(tempBytes, length) + + // Maintain a memory counter for the current write location in the + // temp bytes array by adding the 32 bytes for the array length to + // the starting location. + let mc := add(tempBytes, 0x20) + // Stop copying when the memory counter reaches the length of the + // first bytes array. + let end := add(mc, length) + + for { + // Initialize a copy counter to the start of the _preBytes data, + // 32 bytes into its memory. + let cc := add(_preBytes, 0x20) + } lt(mc, end) { + // Increase both counters by 32 bytes each iteration. + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + // Write the _preBytes data into the tempBytes memory 32 bytes + // at a time. + mstore(mc, mload(cc)) + } + + // Add the length of _postBytes to the current length of tempBytes + // and store it as the new length in the first 32 bytes of the + // tempBytes memory. + length := mload(_postBytes) + mstore(tempBytes, add(length, mload(tempBytes))) + + // Move the memory counter back from a multiple of 0x20 to the + // actual end of the _preBytes data. + mc := end + // Stop copying when the memory counter reaches the new combined + // length of the arrays. + end := add(mc, length) + + for { + let cc := add(_postBytes, 0x20) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + // Update the free-memory pointer by padding our last write location + // to 32 bytes: add 31 bytes to the end of tempBytes to move to the + // next 32 byte block, then round down to the nearest multiple of + // 32. If the sum of the length of the two arrays is zero then add + // one before rounding down to leave a blank 32 bytes (the length block with 0). + mstore( + 0x40, + and( + add(add(end, iszero(add(length, mload(_preBytes)))), 31), + not(31) // Round down to the nearest 32 bytes. + ) + ) + } + return tempBytes; } - return tempBytes; - } - - function slice( - bytes memory _bytes, - uint _start, - uint _length - ) internal pure returns (bytes memory) { - require(_bytes.length >= (_start + _length)); - bytes memory tempBytes; - assembly { - switch iszero(_length) - case 0 { - // Get a location of some free memory and store it in tempBytes as - // Solidity does for memory variables. - tempBytes := mload(0x40) - - // The first word of the slice result is potentially a partial - // word read from the original array. To read it, we calculate - // the length of that partial word and start copying that many - // bytes into the array. The first word we copy will start with - // data we don't care about, but the last `lengthmod` bytes will - // land at the beginning of the contents of the new array. When - // we're done copying, we overwrite the full first word with - // the actual length of the slice. - let lengthmod := and(_length, 31) - - // The multiplication in the next line is necessary - // because when slicing multiples of 32 bytes (lengthmod == 0) - // the following copy loop was copying the origin's length - // and then ending prematurely not copying everything it should. - let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) - let end := add(mc, _length) - - for { - // The multiplication in the next line has the same exact purpose - // as the one above. - let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) - } lt(mc, end) { - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) + + function slice(bytes memory _bytes, uint256 _start, uint256 _length) + internal + pure + returns (bytes memory) + { + require(_bytes.length >= (_start + _length)); + bytes memory tempBytes; + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add( + add(tempBytes, lengthmod), + mul(0x20, iszero(lengthmod)) + ) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add( + add( + add(_bytes, lengthmod), + mul(0x20, iszero(lengthmod)) + ), + _start + ) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + mstore(0x40, add(tempBytes, 0x20)) + } } - mstore(tempBytes, _length) - - //update free-memory pointer - //allocating the array padded to 32 bytes like the compiler does now - mstore(0x40, and(add(mc, 31), not(31))) - } - //if we want a zero-length slice let's just return a zero-length array - default { - tempBytes := mload(0x40) - mstore(0x40, add(tempBytes, 0x20)) - } + return tempBytes; } - return tempBytes; - } - - // Pad a bytes array to 32 bytes - function leftPad(bytes memory _bytes) internal pure returns (bytes memory) { - // may underflow if bytes.length < 32. Hence using SafeMath.sub - bytes memory newBytes = new bytes(SafeMath.sub(32, _bytes.length)); - return concat(newBytes, _bytes); - } - - function toBytes32(bytes memory b) internal pure returns (bytes32) { - require(b.length >= 32, "Bytes array should atleast be 32 bytes"); - bytes32 out; - for (uint i = 0; i < 32; i++) { - out |= bytes32(b[i] & 0xFF) >> (i * 8); + // Pad a bytes array to 32 bytes + function leftPad(bytes memory _bytes) internal pure returns (bytes memory) { + // may underflow if bytes.length < 32. Hence using SafeMath.sub + bytes memory newBytes = new bytes(SafeMath.sub(32, _bytes.length)); + return concat(newBytes, _bytes); } - return out; - } - function toBytes4(bytes memory b) internal pure returns (bytes4 result) { - assembly { - result := mload(add(b, 32)) + function toBytes32(bytes memory b) internal pure returns (bytes32) { + require(b.length >= 32, "Bytes array should atleast be 32 bytes"); + bytes32 out; + for (uint256 i = 0; i < 32; i++) { + out |= bytes32(b[i] & 0xFF) >> (i * 8); + } + return out; } - } - function fromBytes32(bytes32 x) internal pure returns (bytes memory) { - bytes memory b = new bytes(32); - for (uint i = 0; i < 32; i++) { - b[i] = byte(uint8(uint(x) / (2**(8*(31 - i))))); + function toBytes4(bytes memory b) internal pure returns (bytes4 result) { + assembly { + result := mload(add(b, 32)) + } + } + + function fromBytes32(bytes32 x) internal pure returns (bytes memory) { + bytes memory b = new bytes(32); + for (uint256 i = 0; i < 32; i++) { + b[i] = bytes1(uint8(uint256(x) / (2**(8 * (31 - i))))); + } + return b; } - return b; - } - - function fromUint(uint256 _num) internal pure returns (bytes memory _ret) { - _ret = new bytes(32); - assembly { mstore(add(_ret, 32), _num) } - } - - function toUint(bytes memory _bytes, uint _start) internal pure returns (uint256) { - require(_bytes.length >= (_start + 32)); - uint256 tempUint; - assembly { - tempUint := mload(add(add(_bytes, 0x20), _start)) + + function fromUint(uint256 _num) internal pure returns (bytes memory _ret) { + _ret = new bytes(32); + assembly { + mstore(add(_ret, 32), _num) + } } - return tempUint; - } - - function toAddress(bytes memory _bytes, uint _start) internal pure returns (address) { - require(_bytes.length >= (_start + 20)); - address tempAddress; - assembly { - tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) + + function toUint(bytes memory _bytes, uint256 _start) + internal + pure + returns (uint256) + { + require(_bytes.length >= (_start + 32)); + uint256 tempUint; + assembly { + tempUint := mload(add(add(_bytes, 0x20), _start)) + } + return tempUint; } - return tempAddress; - } + function toAddress(bytes memory _bytes, uint256 _start) + internal + pure + returns (address) + { + require(_bytes.length >= (_start + 20)); + address tempAddress; + assembly { + tempAddress := div( + mload(add(add(_bytes, 0x20), _start)), + 0x1000000000000000000000000 + ) + } + + return tempAddress; + } } diff --git a/contracts/common/lib/Common.sol b/contracts/common/lib/Common.sol index 00f2d717c..deb1b1b2c 100644 --- a/contracts/common/lib/Common.sol +++ b/contracts/common/lib/Common.sol @@ -2,32 +2,34 @@ pragma solidity ^0.5.2; import "./BytesLib.sol"; - library Common { - function getV(bytes memory v, uint16 chainId) public pure returns (uint8) { - if (chainId > 0) { - return uint8(BytesLib.toUint(BytesLib.leftPad(v), 0) - (chainId * 2) - 8); - } else { - return uint8(BytesLib.toUint(BytesLib.leftPad(v), 0)); + function getV(bytes memory v, uint16 chainId) public pure returns (uint8) { + if (chainId > 0) { + return + uint8( + BytesLib.toUint(BytesLib.leftPad(v), 0) - (chainId * 2) - 8 + ); + } else { + return uint8(BytesLib.toUint(BytesLib.leftPad(v), 0)); + } } - } - //assemble the given address bytecode. If bytecode exists then the _addr is a contract. - function isContract(address _addr) public view returns (bool) { - uint length; - assembly { - //retrieve the size of the code on target address, this needs assembly - length := extcodesize(_addr) + //assemble the given address bytecode. If bytecode exists then the _addr is a contract. + function isContract(address _addr) public view returns (bool) { + uint256 length; + assembly { + //retrieve the size of the code on target address, this needs assembly + length := extcodesize(_addr) + } + return (length > 0); } - return (length > 0); - } - // convert bytes to uint8 - function toUint8(bytes memory _arg) public pure returns (uint8) { - return uint8(_arg[0]); - } + // convert bytes to uint8 + function toUint8(bytes memory _arg) public pure returns (uint8) { + return uint8(_arg[0]); + } - function toUint16(bytes memory _arg) public pure returns (uint16) { - return (uint16(uint8(_arg[0])) << 8) | uint16(uint8(_arg[1])); - } + function toUint16(bytes memory _arg) public pure returns (uint16) { + return (uint16(uint8(_arg[0])) << 8) | uint16(uint8(_arg[1])); + } } diff --git a/contracts/common/lib/Merkle.sol b/contracts/common/lib/Merkle.sol index 74e4f30d5..bdf5cb246 100644 --- a/contracts/common/lib/Merkle.sol +++ b/contracts/common/lib/Merkle.sol @@ -1,31 +1,34 @@ pragma solidity ^0.5.2; - library Merkle { - function checkMembership( - bytes32 leaf, - uint256 mainIndex, - bytes32 rootHash, - bytes memory proof - ) public pure returns (bool) { - bytes32 proofElement; - bytes32 computedHash = leaf; - require(proof.length % 32 == 0, "Invalid proof length"); + function checkMembership( + bytes32 leaf, + uint256 mainIndex, + bytes32 rootHash, + bytes memory proof + ) public pure returns (bool) { + bytes32 proofElement; + bytes32 computedHash = leaf; + require(proof.length % 32 == 0, "Invalid proof length"); - uint256 index = mainIndex; - for (uint256 i = 32; i <= proof.length; i += 32) { - assembly { - proofElement := mload(add(proof, i)) - } + uint256 index = mainIndex; + for (uint256 i = 32; i <= proof.length; i += 32) { + assembly { + proofElement := mload(add(proof, i)) + } - if (index % 2 == 0) { - computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); - } else { - computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); - } + if (index % 2 == 0) { + computedHash = keccak256( + abi.encodePacked(computedHash, proofElement) + ); + } else { + computedHash = keccak256( + abi.encodePacked(proofElement, computedHash) + ); + } - index = index / 2; + index = index / 2; + } + return computedHash == rootHash; } - return computedHash == rootHash; - } } diff --git a/contracts/common/lib/MerklePatriciaProof.sol b/contracts/common/lib/MerklePatriciaProof.sol index 16be7f843..9c8029f9b 100644 --- a/contracts/common/lib/MerklePatriciaProof.sol +++ b/contracts/common/lib/MerklePatriciaProof.sol @@ -6,11 +6,10 @@ */ pragma solidity ^0.5.2; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; - +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; library MerklePatriciaProof { - /* + /* * @dev Verifies a merkle patricia proof. * @param value The terminating value in the trie. * @param encodedPath The path in the trie leading to value. @@ -18,127 +17,155 @@ library MerklePatriciaProof { * @param root The root hash of the trie. * @return The boolean validity of the proof. */ - function verify( - bytes memory value, - bytes memory encodedPath, - bytes memory rlpParentNodes, - bytes32 root - ) internal pure returns (bool) { - RLPReader.RLPItem memory item = RLPReader.toRlpItem(rlpParentNodes); - RLPReader.RLPItem[] memory parentNodes = RLPReader.toList(item); - - bytes memory currentNode; - RLPReader.RLPItem[] memory currentNodeList; - - bytes32 nodeKey = root; - uint pathPtr = 0; - - bytes memory path = _getNibbleArray(encodedPath); - if (path.length == 0) { - return false; - } - - for (uint i = 0; i < parentNodes.length; i++) { - if (pathPtr > path.length) { - return false; - } - - currentNode = RLPReader.toRlpBytes(parentNodes[i]); - if (nodeKey != keccak256(currentNode)) { - return false; - } - currentNodeList = RLPReader.toList(parentNodes[i]); - - if (currentNodeList.length == 17) { - if (pathPtr == path.length) { - if (keccak256(RLPReader.toBytes(currentNodeList[16])) == keccak256(value)) { - return true; - } else { + function verify( + bytes memory value, + bytes memory encodedPath, + bytes memory rlpParentNodes, + bytes32 root + ) internal pure returns (bool) { + RLPReader.RLPItem memory item = RLPReader.toRlpItem(rlpParentNodes); + RLPReader.RLPItem[] memory parentNodes = RLPReader.toList(item); + + bytes memory currentNode; + RLPReader.RLPItem[] memory currentNodeList; + + bytes32 nodeKey = root; + uint256 pathPtr = 0; + + bytes memory path = _getNibbleArray(encodedPath); + if (path.length == 0) { return false; - } } - uint8 nextPathNibble = uint8(path[pathPtr]); - if (nextPathNibble > 16) { - return false; - } - nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[nextPathNibble])); - pathPtr += 1; - } else if (currentNodeList.length == 2) { - pathPtr += _nibblesToTraverse(RLPReader.toBytes(currentNodeList[0]), path, pathPtr); - if (pathPtr == path.length) {//leaf node - if (keccak256(RLPReader.toBytes(currentNodeList[1])) == keccak256(value)) { - return true; - } else { - return false; - } + for (uint256 i = 0; i < parentNodes.length; i++) { + if (pathPtr > path.length) { + return false; + } + + currentNode = RLPReader.toRlpBytes(parentNodes[i]); + if (nodeKey != keccak256(currentNode)) { + return false; + } + currentNodeList = RLPReader.toList(parentNodes[i]); + + if (currentNodeList.length == 17) { + if (pathPtr == path.length) { + if ( + keccak256(RLPReader.toBytes(currentNodeList[16])) == + keccak256(value) + ) { + return true; + } else { + return false; + } + } + + uint8 nextPathNibble = uint8(path[pathPtr]); + if (nextPathNibble > 16) { + return false; + } + nodeKey = bytes32( + RLPReader.toUintStrict(currentNodeList[nextPathNibble]) + ); + pathPtr += 1; + } else if (currentNodeList.length == 2) { + pathPtr += _nibblesToTraverse( + RLPReader.toBytes(currentNodeList[0]), + path, + pathPtr + ); + if (pathPtr == path.length) { + //leaf node + if ( + keccak256(RLPReader.toBytes(currentNodeList[1])) == + keccak256(value) + ) { + return true; + } else { + return false; + } + } + + //extension node + if ( + _nibblesToTraverse( + RLPReader.toBytes(currentNodeList[0]), + path, + pathPtr + ) == + 0 + ) { + return false; + } + + nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[1])); + } else { + return false; + } } + } - //extension node - if (_nibblesToTraverse(RLPReader.toBytes(currentNodeList[0]), path, pathPtr) == 0) { - return false; + function _nibblesToTraverse( + bytes memory encodedPartialPath, + bytes memory path, + uint256 pathPtr + ) private pure returns (uint256) { + uint256 len; + // encodedPartialPath has elements that are each two hex characters (1 byte), but partialPath + // and slicedPath have elements that are each one hex character (1 nibble) + bytes memory partialPath = _getNibbleArray(encodedPartialPath); + bytes memory slicedPath = new bytes(partialPath.length); + + // pathPtr counts nibbles in path + // partialPath.length is a number of nibbles + for (uint256 i = pathPtr; i < pathPtr + partialPath.length; i++) { + bytes1 pathNibble = path[i]; + slicedPath[i - pathPtr] = pathNibble; } - nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[1])); - } else { - return false; - } - } - } - - function _nibblesToTraverse( - bytes memory encodedPartialPath, - bytes memory path, - uint pathPtr - ) private pure returns (uint) { - uint len; - // encodedPartialPath has elements that are each two hex characters (1 byte), but partialPath - // and slicedPath have elements that are each one hex character (1 nibble) - bytes memory partialPath = _getNibbleArray(encodedPartialPath); - bytes memory slicedPath = new bytes(partialPath.length); - - // pathPtr counts nibbles in path - // partialPath.length is a number of nibbles - for (uint i = pathPtr; i < pathPtr+partialPath.length; i++) { - byte pathNibble = path[i]; - slicedPath[i-pathPtr] = pathNibble; + if (keccak256(partialPath) == keccak256(slicedPath)) { + len = partialPath.length; + } else { + len = 0; + } + return len; } - if (keccak256(partialPath) == keccak256(slicedPath)) { - len = partialPath.length; - } else { - len = 0; + // bytes b must be hp encoded + function _getNibbleArray(bytes memory b) + private + pure + returns (bytes memory) + { + bytes memory nibbles; + if (b.length > 0) { + uint8 offset; + uint8 hpNibble = uint8(_getNthNibbleOfBytes(0, b)); + if (hpNibble == 1 || hpNibble == 3) { + nibbles = new bytes(b.length * 2 - 1); + bytes1 oddNibble = _getNthNibbleOfBytes(1, b); + nibbles[0] = oddNibble; + offset = 1; + } else { + nibbles = new bytes(b.length * 2 - 2); + offset = 0; + } + + for (uint256 i = offset; i < nibbles.length; i++) { + nibbles[i] = _getNthNibbleOfBytes(i - offset + 2, b); + } + } + return nibbles; } - return len; - } - - // bytes b must be hp encoded - function _getNibbleArray(bytes memory b) private pure returns (bytes memory) { - bytes memory nibbles; - if (b.length > 0) { - uint8 offset; - uint8 hpNibble = uint8(_getNthNibbleOfBytes(0, b)); - if (hpNibble == 1 || hpNibble == 3) { - nibbles = new bytes(b.length*2-1); - byte oddNibble = _getNthNibbleOfBytes(1, b); - nibbles[0] = oddNibble; - offset = 1; - } else { - nibbles = new bytes(b.length * 2-2); - offset = 0; - } - - for (uint i = offset; i < nibbles.length; i++) { - nibbles[i] = _getNthNibbleOfBytes(i-offset + 2, b); - } + + function _getNthNibbleOfBytes(uint256 n, bytes memory str) + private + pure + returns (bytes1) + { + return + bytes1( + n % 2 == 0 ? uint8(str[n / 2]) / 0x10 : uint8(str[n / 2]) % 0x10 + ); } - return nibbles; - } - - function _getNthNibbleOfBytes( - uint n, - bytes memory str - ) private pure returns (byte) { - return byte(n % 2 == 0 ? uint8(str[n/2]) / 0x10 : uint8(str[n/2]) % 0x10); - } } diff --git a/contracts/common/lib/PriorityQueue.sol b/contracts/common/lib/PriorityQueue.sol index 0fe638c36..515c103de 100644 --- a/contracts/common/lib/PriorityQueue.sol +++ b/contracts/common/lib/PriorityQueue.sol @@ -1,120 +1,122 @@ pragma solidity ^0.5.2; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; - - +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** * @title PriorityQueue * @dev A priority queue implementation. */ contract PriorityQueue is Ownable { - using SafeMath for uint256; + using SafeMath for uint256; - uint256[] heapList; - uint256 public currentSize; + uint256[] heapList; + uint256 public currentSize; - constructor() public { - heapList = [0]; - } + constructor() public { + heapList = [0]; + } - /** + /** * @dev Inserts an element into the priority queue. * @param _priority Priority to insert. * @param _value Some additional value. */ - function insert(uint256 _priority, uint256 _value) public onlyOwner { - uint256 element = _priority << 128 | _value; - heapList.push(element); - currentSize = currentSize.add(1); - _percUp(currentSize); - } + function insert(uint256 _priority, uint256 _value) public onlyOwner { + uint256 element = (_priority << 128) | _value; + heapList.push(element); + currentSize = currentSize.add(1); + _percUp(currentSize); + } - /** + /** * @dev Returns the top element of the heap. * @return The smallest element in the priority queue. */ - function getMin() public view returns (uint256, uint256) { - return _splitElement(heapList[1]); - } + function getMin() public view returns (uint256, uint256) { + return _splitElement(heapList[1]); + } - /** + /** * @dev Deletes the top element of the heap and shifts everything up. * @return The smallest element in the priorty queue. */ - function delMin() public onlyOwner returns (uint256, uint256) { - uint256 retVal = heapList[1]; - heapList[1] = heapList[currentSize]; - delete heapList[currentSize]; - currentSize = currentSize.sub(1); - _percDown(1); - heapList.length = heapList.length.sub(1); - return _splitElement(retVal); - } + function delMin() public onlyOwner returns (uint256, uint256) { + uint256 retVal = heapList[1]; + heapList[1] = heapList[currentSize]; + delete heapList[currentSize]; + currentSize = currentSize.sub(1); + _percDown(1); + heapList.length = heapList.length.sub(1); + return _splitElement(retVal); + } - /** + /** * @dev Determines the minimum child of a given node in the tree. * @param _index Index of the node in the tree. * @return The smallest child node. */ - function _minChild(uint256 _index) private view returns (uint256) { - if (_index.mul(2).add(1) > currentSize) { - return _index.mul(2); - } else { - if (heapList[_index.mul(2)] < heapList[_index.mul(2).add(1)]) { - return _index.mul(2); - } else { - return _index.mul(2).add(1); - } + function _minChild(uint256 _index) private view returns (uint256) { + if (_index.mul(2).add(1) > currentSize) { + return _index.mul(2); + } else { + if (heapList[_index.mul(2)] < heapList[_index.mul(2).add(1)]) { + return _index.mul(2); + } else { + return _index.mul(2).add(1); + } + } } - } - /** + /** * @dev Bubbles the element at some index up. */ - function _percUp(uint256 _index) private { - uint256 index = _index; - uint256 j = index; - uint256 newVal = heapList[index]; - - while (newVal < heapList[index.div(2)]) { - heapList[index] = heapList[index.div(2)]; - index = index.div(2); - } - - if (index != j) { - heapList[index] = newVal; + function _percUp(uint256 _index) private { + uint256 index = _index; + uint256 j = index; + uint256 newVal = heapList[index]; + + while (newVal < heapList[index.div(2)]) { + heapList[index] = heapList[index.div(2)]; + index = index.div(2); + } + + if (index != j) { + heapList[index] = newVal; + } } - } - /** + /** * @dev Bubbles the element at some index down. */ - function _percDown(uint256 _index) private { - uint256 index = _index; - uint256 j = index; - uint256 newVal = heapList[index]; - uint256 mc = _minChild(index); - while (mc <= currentSize && newVal > heapList[mc]) { - heapList[index] = heapList[mc]; - index = mc; - mc = _minChild(index); + function _percDown(uint256 _index) private { + uint256 index = _index; + uint256 j = index; + uint256 newVal = heapList[index]; + uint256 mc = _minChild(index); + while (mc <= currentSize && newVal > heapList[mc]) { + heapList[index] = heapList[mc]; + index = mc; + mc = _minChild(index); + } + + if (index != j) { + heapList[index] = newVal; + } } - if (index != j) { - heapList[index] = newVal; - } - } - - /** + /** * @dev Split an element into its priority and value. * @param _element Element to decode. * @return A tuple containing the priority and value. */ - function _splitElement(uint256 _element) private pure returns (uint256, uint256) { - uint256 priority = _element >> 128; - uint256 value = uint256(uint128(_element)); - return (priority, value); - } + function _splitElement(uint256 _element) + private + pure + returns (uint256, uint256) + { + uint256 priority = _element >> 128; + uint256 value = uint256(uint128(_element)); + return (priority, value); + } } diff --git a/contracts/common/lib/RLPEncode.sol b/contracts/common/lib/RLPEncode.sol index 3eda1e99d..668009309 100644 --- a/contracts/common/lib/RLPEncode.sol +++ b/contracts/common/lib/RLPEncode.sol @@ -3,83 +3,92 @@ // [Very] modified version of Sam Mayo's library. pragma solidity ^0.5.2; - import "./BytesLib.sol"; - library RLPEncode { + // Encode an item (bytes memory) + function encodeItem(bytes memory self) + internal + pure + returns (bytes memory) + { + bytes memory encoded; + if (self.length == 1 && uint8(self[0] & 0xFF) < 0x80) { + encoded = new bytes(1); + encoded = self; + } else { + encoded = BytesLib.concat(encodeLength(self.length, 128), self); + } + return encoded; + } - // Encode an item (bytes memory) - function encodeItem(bytes memory self) internal pure returns (bytes memory) { - bytes memory encoded; - if (self.length == 1 && uint8(self[0] & 0xFF) < 0x80) { - encoded = new bytes(1); - encoded = self; - } else { - encoded = BytesLib.concat(encodeLength(self.length, 128), self); - } - return encoded; - } - - // Encode a list of items - function encodeList(bytes[] memory self) internal pure returns (bytes memory) { - bytes memory encoded; - for (uint i = 0; i < self.length; i++) { - encoded = BytesLib.concat(encoded, encodeItem(self[i])); + // Encode a list of items + function encodeList(bytes[] memory self) + internal + pure + returns (bytes memory) + { + bytes memory encoded; + for (uint256 i = 0; i < self.length; i++) { + encoded = BytesLib.concat(encoded, encodeItem(self[i])); + } + return BytesLib.concat(encodeLength(encoded.length, 192), encoded); } - return BytesLib.concat(encodeLength(encoded.length, 192), encoded); - } - // Hack to encode nested lists. If you have a list as an item passed here, included - // pass = true in that index. E.g. - // [item, list, item] --> pass = [false, true, false] - // function encodeListWithPasses(bytes[] memory self, bool[] pass) internal pure returns (bytes memory) { - // bytes memory encoded; - // for (uint i=0; i < self.length; i++) { - // if (pass[i] == true) { - // encoded = BytesLib.concat(encoded, self[i]); - // } else { - // encoded = BytesLib.concat(encoded, encodeItem(self[i])); - // } - // } - // return BytesLib.concat(encodeLength(encoded.length, 192), encoded); - // } + // Hack to encode nested lists. If you have a list as an item passed here, included + // pass = true in that index. E.g. + // [item, list, item] --> pass = [false, true, false] + // function encodeListWithPasses(bytes[] memory self, bool[] pass) internal pure returns (bytes memory) { + // bytes memory encoded; + // for (uint i=0; i < self.length; i++) { + // if (pass[i] == true) { + // encoded = BytesLib.concat(encoded, self[i]); + // } else { + // encoded = BytesLib.concat(encoded, encodeItem(self[i])); + // } + // } + // return BytesLib.concat(encodeLength(encoded.length, 192), encoded); + // } - // Generate the prefix for an item or the entire list based on RLP spec - function encodeLength(uint256 L, uint256 offset) internal pure returns (bytes memory) { - if (L < 56) { - bytes memory prefix = new bytes(1); - prefix[0] = byte(uint8(L + offset)); - return prefix; - } else { - // lenLen is the length of the hex representation of the data length - uint lenLen; - uint i = 0x1; + // Generate the prefix for an item or the entire list based on RLP spec + function encodeLength(uint256 L, uint256 offset) + internal + pure + returns (bytes memory) + { + if (L < 56) { + bytes memory prefix = new bytes(1); + prefix[0] = bytes1(uint8(L + offset)); + return prefix; + } else { + // lenLen is the length of the hex representation of the data length + uint256 lenLen; + uint256 i = 0x1; - while (L/i != 0) { - lenLen++; - i *= 0x100; - } + while (L / i != 0) { + lenLen++; + i *= 0x100; + } - bytes memory prefix0 = getLengthBytes(offset + 55 + lenLen); - bytes memory prefix1 = getLengthBytes(L); - return BytesLib.concat(prefix0, prefix1); + bytes memory prefix0 = getLengthBytes(offset + 55 + lenLen); + bytes memory prefix1 = getLengthBytes(L); + return BytesLib.concat(prefix0, prefix1); + } } - } - function getLengthBytes(uint256 x) internal pure returns (bytes memory b) { - // Figure out if we need 1 or two bytes to express the length. - // 1 byte gets us to max 255 - // 2 bytes gets us to max 65535 (no payloads will be larger than this) - uint256 nBytes = 1; - if (x > 255) { - nBytes = 2; - } + function getLengthBytes(uint256 x) internal pure returns (bytes memory b) { + // Figure out if we need 1 or two bytes to express the length. + // 1 byte gets us to max 255 + // 2 bytes gets us to max 65535 (no payloads will be larger than this) + uint256 nBytes = 1; + if (x > 255) { + nBytes = 2; + } - b = new bytes(nBytes); - // Encode the length and return it - for (uint i = 0; i < nBytes; i++) { - b[i] = byte(uint8(x / (2**(8*(nBytes - 1 - i))))); + b = new bytes(nBytes); + // Encode the length and return it + for (uint256 i = 0; i < nBytes; i++) { + b[i] = bytes1(uint8(x / (2**(8 * (nBytes - 1 - i))))); + } } - } } diff --git a/contracts/common/misc/ContractReceiver.sol b/contracts/common/misc/ContractReceiver.sol index 0caf2b4a7..453acef51 100644 --- a/contracts/common/misc/ContractReceiver.sol +++ b/contracts/common/misc/ContractReceiver.sol @@ -1,15 +1,15 @@ pragma solidity ^0.5.2; - /* +/* * Contract that is working with ERC223 tokens */ - /// @title ContractReceiver - Standard contract implementation for compatibility with ERC 223 tokens. contract ContractReceiver { - /// @dev Function that is called when a user or another contract wants to transfer funds. - /// @param _from Transaction initiator, analogue of msg.sender - /// @param _value Number of tokens to transfer. - /// @param _data Data containig a function signature and/or parameters - function tokenFallback(address _from, uint256 _value, bytes memory _data) public; + /// @dev Function that is called when a user or another contract wants to transfer funds. + /// @param _from Transaction initiator, analogue of msg.sender + /// @param _value Number of tokens to transfer. + /// @param _data Data containig a function signature and/or parameters + function tokenFallback(address _from, uint256 _value, bytes memory _data) + public; } diff --git a/contracts/common/misc/DelegateProxy.sol b/contracts/common/misc/DelegateProxy.sol index a99110951..c199eb41e 100644 --- a/contracts/common/misc/DelegateProxy.sol +++ b/contracts/common/misc/DelegateProxy.sol @@ -1,32 +1,41 @@ pragma solidity ^0.5.2; -import { ProxyStorage } from "./ProxyStorage.sol"; -import { ERCProxy } from "./ERCProxy.sol"; - +import {ProxyStorage} from "./ProxyStorage.sol"; +import {ERCProxy} from "./ERCProxy.sol"; contract DelegateProxy is ERCProxy { + function proxyType() external pure returns (uint256 proxyTypeId) { + // Upgradeable proxy + proxyTypeId = 2; + } - function proxyType() external pure returns (uint256 proxyTypeId) { - // Upgradeable proxy - proxyTypeId = 2; - } - - function implementation() external view returns(address); - - function delegatedFwd(address _dst, bytes memory _calldata) internal { - - // solium-disable-next-line security/no-inline-assembly - assembly { - let result := delegatecall(sub(gas, 10000), _dst, add(_calldata, 0x20), mload(_calldata), 0, 0) - let size := returndatasize - - let ptr := mload(0x40) - returndatacopy(ptr, 0, size) - - // revert instead of invalid() bc if the underlying call failed with invalid() it already wasted gas. - // if the call returned error data, forward it - switch result case 0 { revert(ptr, size) } - default { return(ptr, size) } + function implementation() external view returns (address); + + function delegatedFwd(address _dst, bytes memory _calldata) internal { + // solium-disable-next-line security/no-inline-assembly + assembly { + let result := delegatecall( + sub(gas, 10000), + _dst, + add(_calldata, 0x20), + mload(_calldata), + 0, + 0 + ) + let size := returndatasize + + let ptr := mload(0x40) + returndatacopy(ptr, 0, size) + + // revert instead of invalid() bc if the underlying call failed with invalid() it already wasted gas. + // if the call returned error data, forward it + switch result + case 0 { + revert(ptr, size) + } + default { + return(ptr, size) + } + } } - } } diff --git a/contracts/common/misc/ERCProxy.sol b/contracts/common/misc/ERCProxy.sol index ec960ccb3..ef369eb65 100644 --- a/contracts/common/misc/ERCProxy.sol +++ b/contracts/common/misc/ERCProxy.sol @@ -7,6 +7,6 @@ pragma solidity ^0.5.2; // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-897.md interface ERCProxy { - function proxyType() external pure returns (uint256 proxyTypeId); - function implementation() external view returns (address codeAddr); + function proxyType() external pure returns (uint256 proxyTypeId); + function implementation() external view returns (address codeAddr); } diff --git a/contracts/common/misc/Proxy.sol b/contracts/common/misc/Proxy.sol index 85db5b2e3..88d78a8eb 100644 --- a/contracts/common/misc/Proxy.sol +++ b/contracts/common/misc/Proxy.sol @@ -1,53 +1,44 @@ pragma solidity ^0.5.2; -import { DelegateProxy } from "./DelegateProxy.sol"; -import { ProxyStorage } from "./ProxyStorage.sol"; - +import {DelegateProxy} from "./DelegateProxy.sol"; +import {ProxyStorage} from "./ProxyStorage.sol"; contract Proxy is ProxyStorage, DelegateProxy { + event ProxyUpdated(address indexed _new, address indexed _old); + event OwnerUpdate(address _prevOwner, address _newOwner); - event ProxyUpdated(address indexed _new, address indexed _old); - event OwnerUpdate(address _prevOwner, address _newOwner); - + constructor(address _proxyTo) public { + updateImplementation(_proxyTo); + } - constructor(address _proxyTo) public { - updateImplementation(_proxyTo); - } + function() external payable { + // require(currentContract != 0, "If app code has not been set yet, do not call"); + // Todo: filter out some calls or handle in the end fallback + delegatedFwd(proxyTo, msg.data); + } - function () external payable { - // require(currentContract != 0, "If app code has not been set yet, do not call"); - // Todo: filter out some calls or handle in the end fallback - delegatedFwd(proxyTo, msg.data); - } + function implementation() external view returns (address) { + return proxyTo; + } - function implementation() - external - view - returns(address) - { - return proxyTo; - } + function updateImplementation(address _newProxyTo) public onlyOwner { + require(_newProxyTo != address(0x0), "INVALID_PROXY_ADDRESS"); + require( + isContract(_newProxyTo), + "DESTINATION_ADDRESS_IS_NOT_A_CONTRACT" + ); + emit ProxyUpdated(_newProxyTo, proxyTo); + proxyTo = _newProxyTo; + } - function updateImplementation(address _newProxyTo) - public - onlyOwner - { - require(_newProxyTo != address(0x0), "INVALID_PROXY_ADDRESS"); - require(isContract(_newProxyTo), "DESTINATION_ADDRESS_IS_NOT_A_CONTRACT"); - emit ProxyUpdated(_newProxyTo, proxyTo); - proxyTo = _newProxyTo; - } + function isContract(address _target) internal view returns (bool) { + if (_target == address(0)) { + return false; + } - function isContract(address _target) - internal - view - returns(bool) - { - if (_target == address(0)) { - return false; + uint256 size; + assembly { + size := extcodesize(_target) + } + return size > 0; } - - uint256 size; - assembly { size := extcodesize(_target) } - return size > 0; - } } diff --git a/contracts/common/misc/ProxyStorage.sol b/contracts/common/misc/ProxyStorage.sol index 3329a329c..2dcf41b3e 100644 --- a/contracts/common/misc/ProxyStorage.sol +++ b/contracts/common/misc/ProxyStorage.sol @@ -1,7 +1,6 @@ pragma solidity ^0.5.2; -import { Ownable } from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; - +import {Ownable} from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract ProxyStorage is Ownable { - address internal proxyTo; + address internal proxyTo; } diff --git a/contracts/common/mixin/Lockable.sol b/contracts/common/mixin/Lockable.sol index 59cd3d73c..897bec804 100644 --- a/contracts/common/mixin/Lockable.sol +++ b/contracts/common/mixin/Lockable.sol @@ -1,21 +1,20 @@ pragma solidity ^0.5.2; -import { Ownable } from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; - +import {Ownable} from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract Lockable is Ownable { - bool public locked; + bool public locked; - modifier onlyWhenUnlocked() { - require(locked == false); - _; - } + modifier onlyWhenUnlocked() { + require(locked == false); + _; + } - function lock() external onlyOwner { - locked = true; - } + function lock() external onlyOwner { + locked = true; + } - function unlock() external onlyOwner { - locked = false; - } + function unlock() external onlyOwner { + locked = false; + } } diff --git a/contracts/common/mixin/RootChainable.sol b/contracts/common/mixin/RootChainable.sol index 6a1d069e2..efebd091a 100644 --- a/contracts/common/mixin/RootChainable.sol +++ b/contracts/common/mixin/RootChainable.sol @@ -1,36 +1,36 @@ pragma solidity ^0.5.2; -import { Ownable } from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import {Ownable} from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; /** * @title RootChainable */ contract RootChainable is Ownable { - address public rootChain; + address public rootChain; - // Rootchain changed - event RootChainChanged( - address indexed previousRootChain, - address indexed newRootChain - ); + // Rootchain changed + event RootChainChanged( + address indexed previousRootChain, + address indexed newRootChain + ); - // - // Only root chain - // + // + // Only root chain + // - // only root chain - modifier onlyRootChain() { - require(msg.sender == rootChain); - _; - } + // only root chain + modifier onlyRootChain() { + require(msg.sender == rootChain); + _; + } - /** + /** * @dev Allows the current owner to change root chain address. * @param newRootChain The address to new rootchain. */ - function changeRootChain(address newRootChain) public onlyOwner { - require(newRootChain != address(0)); - emit RootChainChanged(rootChain, newRootChain); - rootChain = newRootChain; - } + function changeRootChain(address newRootChain) public onlyOwner { + require(newRootChain != address(0)); + emit RootChainChanged(rootChain, newRootChain); + rootChain = newRootChain; + } } diff --git a/contracts/common/tokens/DummyERC20.sol b/contracts/common/tokens/DummyERC20.sol index aa0ef6497..5d19d5070 100644 --- a/contracts/common/tokens/DummyERC20.sol +++ b/contracts/common/tokens/DummyERC20.sol @@ -1,7 +1,7 @@ pragma solidity ^0.5.2; // import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** override _transfer to avoid emmiting Transfer evnet @@ -17,17 +17,23 @@ interface IERC20 { function approve(address spender, uint256 value) external returns (bool); - function transferFrom(address from, address to, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) + external + returns (bool); function totalSupply() external view returns (uint256); function balanceOf(address who) external view returns (uint256); - function allowance(address owner, address spender) external view returns (uint256); + function allowance(address owner, address spender) + external + view + returns (uint256); // event Transfer(address indexed from, address indexed to, uint256 value); // event Approval(address indexed owner, address indexed spender, uint256 value); + } /** @@ -45,9 +51,9 @@ interface IERC20 { contract DummyERC20 is IERC20 { using SafeMath for uint256; - mapping (address => uint256) private _balances; + mapping(address => uint256) private _balances; - mapping (address => mapping (address => uint256)) private _allowed; + mapping(address => mapping(address => uint256)) private _allowed; uint256 private _totalSupply; // detailed ERC20 @@ -55,15 +61,15 @@ contract DummyERC20 is IERC20 { string public symbol; uint8 public decimals = 18; - constructor (string memory _name, string memory _symbol) public { - name = _name; - symbol = _symbol; + constructor(string memory _name, string memory _symbol) public { + name = _name; + symbol = _symbol; - uint256 value = 10000 * (10 ** 18); - _mint(msg.sender, value); + uint256 value = 10000 * (10**18); + _mint(msg.sender, value); } - /** + /** * @dev Function to mint tokens * @param to The address that will receive the minted tokens. * @param value The amount of tokens to mint. @@ -96,7 +102,11 @@ contract DummyERC20 is IERC20 { * @param spender address The address which will spend the funds. * @return A uint256 specifying the amount of tokens still available for the spender. */ - function allowance(address owner, address spender) public view returns (uint256) { + function allowance(address owner, address spender) + public + view + returns (uint256) + { return _allowed[owner][spender]; } @@ -106,8 +116,8 @@ contract DummyERC20 is IERC20 { * @param value The amount to be transferred. */ function transfer(address to, uint256 value) public returns (bool) { - _transfer(msg.sender, to, value); - return true; + _transfer(msg.sender, to, value); + return true; } /** @@ -132,7 +142,10 @@ contract DummyERC20 is IERC20 { * @param to address The address which you want to transfer to * @param value uint256 the amount of tokens to be transferred */ - function transferFrom(address from, address to, uint256 value) public returns (bool) { + function transferFrom(address from, address to, uint256 value) + public + returns (bool) + { _transfer(from, to, value); _approve(from, msg.sender, _allowed[from][msg.sender].sub(value)); return true; @@ -148,8 +161,15 @@ contract DummyERC20 is IERC20 { * @param spender The address which will spend the funds. * @param addedValue The amount of tokens to increase the allowance by. */ - function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { - _approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue)); + function increaseAllowance(address spender, uint256 addedValue) + public + returns (bool) + { + _approve( + msg.sender, + spender, + _allowed[msg.sender][spender].add(addedValue) + ); return true; } @@ -163,8 +183,15 @@ contract DummyERC20 is IERC20 { * @param spender The address which will spend the funds. * @param subtractedValue The amount of tokens to decrease the allowance by. */ - function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { - _approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue)); + function decreaseAllowance(address spender, uint256 subtractedValue) + public + returns (bool) + { + _approve( + msg.sender, + spender, + _allowed[msg.sender][spender].sub(subtractedValue) + ); return true; } @@ -238,4 +265,3 @@ contract DummyERC20 is IERC20 { _approve(account, msg.sender, _allowed[account][msg.sender].sub(value)); } } - diff --git a/contracts/common/tokens/ERC721PlasmaMintable.sol b/contracts/common/tokens/ERC721PlasmaMintable.sol index 1c127cc14..adfd54849 100644 --- a/contracts/common/tokens/ERC721PlasmaMintable.sol +++ b/contracts/common/tokens/ERC721PlasmaMintable.sol @@ -1,21 +1,27 @@ pragma solidity ^0.5.2; -import { ERC721Mintable } from "openzeppelin-solidity/contracts/token/ERC721/ERC721Mintable.sol"; -import { ERC721MetadataMintable } from "openzeppelin-solidity/contracts/token/ERC721/ERC721MetadataMintable.sol"; -import { ERC721Metadata } from "openzeppelin-solidity/contracts/token/ERC721/ERC721Metadata.sol"; - +import { + ERC721Mintable +} from "openzeppelin-solidity/contracts/token/ERC721/ERC721Mintable.sol"; +import { + ERC721MetadataMintable +} from "openzeppelin-solidity/contracts/token/ERC721/ERC721MetadataMintable.sol"; +import { + ERC721Metadata +} from "openzeppelin-solidity/contracts/token/ERC721/ERC721Metadata.sol"; contract ERC721PlasmaMintable is ERC721Mintable, ERC721MetadataMintable { - constructor (string memory name, string memory symbol) - ERC721Metadata(name, symbol) - public {} + constructor(string memory name, string memory symbol) + public + ERC721Metadata(name, symbol) + {} - /** + /** * @dev Returns whether the specified token exists * @param tokenId uint256 ID of the token to query the existence of * @return bool whether the token exists */ - function exists(uint256 tokenId) public view returns (bool) { - return _exists(tokenId); - } + function exists(uint256 tokenId) public view returns (bool) { + return _exists(tokenId); + } } diff --git a/contracts/common/tokens/MaticWETH.sol b/contracts/common/tokens/MaticWETH.sol index ddf04611d..3d06928a9 100644 --- a/contracts/common/tokens/MaticWETH.sol +++ b/contracts/common/tokens/MaticWETH.sol @@ -1,29 +1,28 @@ pragma solidity ^0.5.2; -import { WETH } from "./WETH.sol"; - +import {WETH} from "./WETH.sol"; contract MaticWETH is WETH { - string public name = "Wrapped Ether"; - string public symbol = "WETH"; - uint8 public decimals = 18; + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; - function deposit() public payable { - _mint(msg.sender, msg.value); - emit Deposit(msg.sender, msg.value); - } + function deposit() public payable { + _mint(msg.sender, msg.value); + emit Deposit(msg.sender, msg.value); + } - function withdraw(uint wad) public { - require(balanceOf(msg.sender) >= wad); - _burn(msg.sender, wad); - msg.sender.transfer(wad); - emit Withdrawal(msg.sender, wad); - } + function withdraw(uint256 wad) public { + require(balanceOf(msg.sender) >= wad); + _burn(msg.sender, wad); + msg.sender.transfer(wad); + emit Withdrawal(msg.sender, wad); + } - function withdraw(uint wad, address user) public { - require(balanceOf(msg.sender) >= wad); - address(uint160(user)).transfer(wad); - _burn(msg.sender, wad); - emit Withdrawal(user, wad); - } + function withdraw(uint256 wad, address user) public { + require(balanceOf(msg.sender) >= wad); + address(uint160(user)).transfer(wad); + _burn(msg.sender, wad); + emit Withdrawal(user, wad); + } } diff --git a/contracts/common/tokens/RootERC721.sol b/contracts/common/tokens/RootERC721.sol index 595ef84fb..5dbf4113b 100644 --- a/contracts/common/tokens/RootERC721.sol +++ b/contracts/common/tokens/RootERC721.sol @@ -1,15 +1,16 @@ pragma solidity ^0.5.2; -import { ERC721Full } from "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol"; - +import { + ERC721Full +} from "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol"; contract RootERC721 is ERC721Full { - constructor (string memory name, string memory symbol) ERC721Full(name, symbol) - public - { - } + constructor(string memory name, string memory symbol) + public + ERC721Full(name, symbol) + {} - function mint(uint256 tokenId) public { - _mint(msg.sender, tokenId); - } + function mint(uint256 tokenId) public { + _mint(msg.sender, tokenId); + } } diff --git a/contracts/common/tokens/TestToken.sol b/contracts/common/tokens/TestToken.sol index 60e8cfecb..667bf8dfe 100644 --- a/contracts/common/tokens/TestToken.sol +++ b/contracts/common/tokens/TestToken.sol @@ -1,23 +1,22 @@ pragma solidity ^0.5.2; -import { ERC20Mintable } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; - +import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; contract TestToken is ERC20Mintable { - // detailed ERC20 - string public name; - string public symbol; - uint8 public decimals = 18; + // detailed ERC20 + string public name; + string public symbol; + uint8 public decimals = 18; - constructor (string memory _name, string memory _symbol) public { - name = _name; - symbol = _symbol; + constructor(string memory _name, string memory _symbol) public { + name = _name; + symbol = _symbol; - uint256 value = 10000 * (10 ** 18); - mint(msg.sender, value); - } + uint256 value = 10000 * (10**18); + mint(msg.sender, value); + } - function () external payable { - mint(msg.sender, msg.value); - } + function() external payable { + mint(msg.sender, msg.value); + } } diff --git a/contracts/common/tokens/WETH.sol b/contracts/common/tokens/WETH.sol index eb2b42d2e..339efac31 100644 --- a/contracts/common/tokens/WETH.sol +++ b/contracts/common/tokens/WETH.sol @@ -2,14 +2,13 @@ pragma solidity ^0.5.2; import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; - contract WETH is ERC20 { - event Deposit(address indexed dst, uint256 wad); - event Withdrawal(address indexed src, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); - function deposit() public payable; + function deposit() public payable; - function withdraw(uint256 wad) public; + function withdraw(uint256 wad) public; - function withdraw(uint256 wad, address user) public; + function withdraw(uint256 wad, address user) public; } diff --git a/contracts/root/IRootChain.sol b/contracts/root/IRootChain.sol index 51e1e3edc..fb5a585ff 100644 --- a/contracts/root/IRootChain.sol +++ b/contracts/root/IRootChain.sol @@ -1,8 +1,12 @@ pragma solidity ^0.5.2; interface IRootChain { - function slash() external; - function submitHeaderBlock(bytes calldata vote, bytes calldata sigs, bytes calldata extradata) external; - function getLastChildBlock() external view returns(uint256); - function currentHeaderBlock() external view returns(uint256); + function slash() external; + function submitHeaderBlock( + bytes calldata vote, + bytes calldata sigs, + bytes calldata extradata + ) external; + function getLastChildBlock() external view returns (uint256); + function currentHeaderBlock() external view returns (uint256); } diff --git a/contracts/root/RootChain.sol b/contracts/root/RootChain.sol index 23da8fbda..3e0d7def0 100644 --- a/contracts/root/RootChain.sol +++ b/contracts/root/RootChain.sol @@ -1,113 +1,134 @@ pragma solidity ^0.5.2; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; -import { Ownable } from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; - -import { RootChainHeader, RootChainStorage } from "./RootChainStorage.sol"; -import { IStakeManager } from "../staking/IStakeManager.sol"; -import { IRootChain } from "./IRootChain.sol"; -import { Registry } from "../common/Registry.sol"; +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; +import {Ownable} from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import {RootChainHeader, RootChainStorage} from "./RootChainStorage.sol"; +import {IStakeManager} from "../staking/IStakeManager.sol"; +import {IRootChain} from "./IRootChain.sol"; +import {Registry} from "../common/Registry.sol"; contract RootChain is RootChainStorage, IRootChain { - using SafeMath for uint256; - using RLPReader for bytes; - using RLPReader for RLPReader.RLPItem; - - modifier onlyDepositManager() { - require( - msg.sender == registry.getDepositManagerAddress(), - "UNAUTHORIZED_DEPOSIT_MANAGER_ONLY" - ); - _; - } - - constructor (address _registry, string memory _heimdallId) public { - registry = Registry(_registry); - heimdallId = keccak256(abi.encodePacked(_heimdallId)); - } - - function submitHeaderBlock( - bytes calldata vote, - bytes calldata sigs, - bytes calldata txData) - external - { - RLPReader.RLPItem[] memory dataList = vote.toRlpItem().toList(); - require(keccak256(dataList[0].toBytes()) == heimdallId, "Chain ID is invalid"); - require(dataList[1].toUint() == VOTE_TYPE, "Vote type is invalid"); - - // validate hash of txData was signed as part of the vote - require(keccak256(dataList[4].toBytes()) == keccak256(abi.encodePacked(sha256(txData))), "Extra data is invalid"); + using SafeMath for uint256; + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + + modifier onlyDepositManager() { + require( + msg.sender == registry.getDepositManagerAddress(), + "UNAUTHORIZED_DEPOSIT_MANAGER_ONLY" + ); + _; + } - RLPReader.RLPItem[] memory extraData = txData.toRlpItem().toList(); - extraData = extraData[0].toList(); - RootChainHeader.HeaderBlock memory headerBlock = _buildHeaderBlock(extraData); - headerBlocks[_nextHeaderBlock] = headerBlock; - // check if it is better to keep it in local storage instead - IStakeManager stakeManager = IStakeManager(registry.getStakeManagerAddress()); - // blockInterval, voteHash, stateRoot, sigs - uint256 _reward = stakeManager.checkSignatures(headerBlock.end.sub(headerBlock.start), keccak256(vote), bytes32(extraData[4].toUint()), sigs); - require(_reward != 0, "Invalid checkpoint"); - emit NewHeaderBlock(headerBlock.proposer, _nextHeaderBlock, _reward, headerBlock.start, headerBlock.end, headerBlock.root); - _nextHeaderBlock = _nextHeaderBlock.add(MAX_DEPOSITS); - _blockDepositId = 1; - } + constructor(address _registry, string memory _heimdallId) public { + registry = Registry(_registry); + heimdallId = keccak256(abi.encodePacked(_heimdallId)); + } - function updateDepositId(uint256 numDeposits) - external - onlyDepositManager - returns(uint256 depositId) - { - depositId = currentHeaderBlock().add(_blockDepositId); - // deposit ids will be (_blockDepositId, _blockDepositId + 1, .... _blockDepositId + numDeposits - 1) - _blockDepositId = _blockDepositId.add(numDeposits); - require( - // Since _blockDepositId is initialized to 1; only (MAX_DEPOSITS - 1) deposits per header block are allowed - _blockDepositId <= MAX_DEPOSITS, - "TOO_MANY_DEPOSITS" - ); - } + function submitHeaderBlock( + bytes calldata vote, + bytes calldata sigs, + bytes calldata txData + ) external { + RLPReader.RLPItem[] memory dataList = vote.toRlpItem().toList(); + require( + keccak256(dataList[0].toBytes()) == heimdallId, + "Chain ID is invalid" + ); + require(dataList[1].toUint() == VOTE_TYPE, "Vote type is invalid"); + + // validate hash of txData was signed as part of the vote + require( + keccak256(dataList[4].toBytes()) == + keccak256(abi.encodePacked(sha256(txData))), + "Extra data is invalid" + ); + + RLPReader.RLPItem[] memory extraData = txData.toRlpItem().toList(); + extraData = extraData[0].toList(); + RootChainHeader.HeaderBlock memory headerBlock = _buildHeaderBlock( + extraData + ); + headerBlocks[_nextHeaderBlock] = headerBlock; + // check if it is better to keep it in local storage instead + IStakeManager stakeManager = IStakeManager( + registry.getStakeManagerAddress() + ); + // blockInterval, voteHash, stateRoot, sigs + uint256 _reward = stakeManager.checkSignatures( + headerBlock.end.sub(headerBlock.start), + keccak256(vote), + bytes32(extraData[4].toUint()), + sigs + ); + require(_reward != 0, "Invalid checkpoint"); + emit NewHeaderBlock( + headerBlock.proposer, + _nextHeaderBlock, + _reward, + headerBlock.start, + headerBlock.end, + headerBlock.root + ); + _nextHeaderBlock = _nextHeaderBlock.add(MAX_DEPOSITS); + _blockDepositId = 1; + } - function getLastChildBlock() external view returns(uint256) { - return headerBlocks[currentHeaderBlock()].end; - } + function updateDepositId(uint256 numDeposits) + external + onlyDepositManager + returns (uint256 depositId) + { + depositId = currentHeaderBlock().add(_blockDepositId); + // deposit ids will be (_blockDepositId, _blockDepositId + 1, .... _blockDepositId + numDeposits - 1) + _blockDepositId = _blockDepositId.add(numDeposits); + require( + // Since _blockDepositId is initialized to 1; only (MAX_DEPOSITS - 1) deposits per header block are allowed + _blockDepositId <= MAX_DEPOSITS, + "TOO_MANY_DEPOSITS" + ); + } - function slash() external { - //TODO: future implementation - } + function getLastChildBlock() external view returns (uint256) { + return headerBlocks[currentHeaderBlock()].end; + } - function currentHeaderBlock() public view returns(uint256) { - return _nextHeaderBlock.sub(MAX_DEPOSITS); - } + function slash() external { + //TODO: future implementation + } - function _buildHeaderBlock(RLPReader.RLPItem[] memory dataList) - private - view - returns(HeaderBlock memory headerBlock) - { - headerBlock.proposer = dataList[0].toAddress(); - // Is this required? Why does a proposer need to be the sender? Think validator relay networks - // require(msg.sender == dataList[0].toAddress(), "Invalid proposer"); + function currentHeaderBlock() public view returns (uint256) { + return _nextHeaderBlock.sub(MAX_DEPOSITS); + } - uint256 nextChildBlock; - /* + function _buildHeaderBlock(RLPReader.RLPItem[] memory dataList) + private + view + returns (HeaderBlock memory headerBlock) + { + headerBlock.proposer = dataList[0].toAddress(); + // Is this required? Why does a proposer need to be the sender? Think validator relay networks + // require(msg.sender == dataList[0].toAddress(), "Invalid proposer"); + + uint256 nextChildBlock; + /* The ID of the 1st header block is MAX_DEPOSITS. if _nextHeaderBlock == MAX_DEPOSITS, then the first header block is yet to be submitted, hence nextChildBlock = 0 */ - if (_nextHeaderBlock > MAX_DEPOSITS) { - nextChildBlock = headerBlocks[currentHeaderBlock()].end + 1; + if (_nextHeaderBlock > MAX_DEPOSITS) { + nextChildBlock = headerBlocks[currentHeaderBlock()].end + 1; + } + require( + nextChildBlock == dataList[1].toUint(), + "INCORRECT_START_BLOCK" + ); + headerBlock.start = nextChildBlock; + headerBlock.end = dataList[2].toUint(); + + // toUintStrict returns the encoded uint. Encoded data must be padded to 32 bytes. + headerBlock.root = bytes32(dataList[3].toUintStrict()); + headerBlock.createdAt = now; } - require( - nextChildBlock == dataList[1].toUint(), - "INCORRECT_START_BLOCK" - ); - headerBlock.start = nextChildBlock; - headerBlock.end = dataList[2].toUint(); - - // toUintStrict returns the encoded uint. Encoded data must be padded to 32 bytes. - headerBlock.root = bytes32(dataList[3].toUintStrict()); - headerBlock.createdAt = now; - } } diff --git a/contracts/root/RootChainStorage.sol b/contracts/root/RootChainStorage.sol index e89ff8809..295238bde 100644 --- a/contracts/root/RootChainStorage.sol +++ b/contracts/root/RootChainStorage.sol @@ -1,36 +1,34 @@ pragma solidity ^0.5.2; -import { Registry } from "../common/Registry.sol"; -import { ProxyStorage } from "../common/misc/ProxyStorage.sol"; - +import {Registry} from "../common/Registry.sol"; +import {ProxyStorage} from "../common/misc/ProxyStorage.sol"; contract RootChainHeader { - event NewHeaderBlock( - address indexed proposer, - uint256 indexed headerBlockId, - uint256 indexed reward, - uint256 start, - uint256 end, - bytes32 root - ); + event NewHeaderBlock( + address indexed proposer, + uint256 indexed headerBlockId, + uint256 indexed reward, + uint256 start, + uint256 end, + bytes32 root + ); - struct HeaderBlock { - bytes32 root; - uint256 start; - uint256 end; - uint256 createdAt; - address proposer; - } + struct HeaderBlock { + bytes32 root; + uint256 start; + uint256 end; + uint256 createdAt; + address proposer; + } } - contract RootChainStorage is ProxyStorage, RootChainHeader { - bytes32 public heimdallId; - uint8 public constant VOTE_TYPE = 2; + bytes32 public heimdallId; + uint8 public constant VOTE_TYPE = 2; - uint16 internal constant MAX_DEPOSITS = 10000; - uint256 internal _nextHeaderBlock = MAX_DEPOSITS; - uint256 internal _blockDepositId = 1; - mapping(uint256 => HeaderBlock) public headerBlocks; - Registry internal registry; + uint16 internal constant MAX_DEPOSITS = 10000; + uint256 internal _nextHeaderBlock = MAX_DEPOSITS; + uint256 internal _blockDepositId = 1; + mapping(uint256 => HeaderBlock) public headerBlocks; + Registry internal registry; } diff --git a/contracts/root/depositManager/DepositManager.sol b/contracts/root/depositManager/DepositManager.sol index 62928074e..8cce3709c 100644 --- a/contracts/root/depositManager/DepositManager.sol +++ b/contracts/root/depositManager/DepositManager.sol @@ -1,135 +1,152 @@ pragma solidity ^0.5.2; -import { IERC721Receiver } from "openzeppelin-solidity/contracts/token/ERC721/IERC721Receiver.sol"; -import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import { IERC721 } from "openzeppelin-solidity/contracts/token/ERC721/IERC721.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; - -import { ContractReceiver } from "../../common/misc/ContractReceiver.sol"; -import { Registry } from "../../common/Registry.sol"; -import { WETH } from "../../common/tokens/WETH.sol"; -import { IDepositManager } from "./IDepositManager.sol"; -import { DepositManagerStorage } from "./DepositManagerStorage.sol"; -import { StateSender } from "../stateSyncer/StateSender.sol"; - - -contract DepositManager is DepositManagerStorage, IDepositManager, IERC721Receiver, ContractReceiver { - using SafeMath for uint256; - - modifier isTokenMapped(address _token) { - require(registry.isTokenMapped(_token), "TOKEN_NOT_SUPPORTED"); - _; - } - - modifier isPredicateAuthorized() { - require( - uint8(registry.predicates(msg.sender)) != 0, - "Not a valid predicate" - ); - _; - } - - // deposit ETH by sending to this contract - function () external payable { - depositEther(); - } - - function transferAssets(address _token, address _user, uint256 _amountOrNFTId) - external - isPredicateAuthorized - { - address wethToken = registry.getWethTokenAddress(); - if (registry.isERC721(_token)) { - IERC721(_token).transferFrom(address(this), _user, _amountOrNFTId); - } else if (_token == wethToken) { - WETH t = WETH(_token); - t.withdraw(_amountOrNFTId, _user); - } else { - require( - IERC20(_token).transfer(_user, _amountOrNFTId), - "TRANSFER_FAILED" - ); +import { + IERC721Receiver +} from "openzeppelin-solidity/contracts/token/ERC721/IERC721Receiver.sol"; +import {IERC20} from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import { + IERC721 +} from "openzeppelin-solidity/contracts/token/ERC721/IERC721.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +import {ContractReceiver} from "../../common/misc/ContractReceiver.sol"; +import {Registry} from "../../common/Registry.sol"; +import {WETH} from "../../common/tokens/WETH.sol"; +import {IDepositManager} from "./IDepositManager.sol"; +import {DepositManagerStorage} from "./DepositManagerStorage.sol"; +import {StateSender} from "../stateSyncer/StateSender.sol"; + +contract DepositManager is + DepositManagerStorage, + IDepositManager, + IERC721Receiver, + ContractReceiver +{ + using SafeMath for uint256; + + modifier isTokenMapped(address _token) { + require(registry.isTokenMapped(_token), "TOKEN_NOT_SUPPORTED"); + _; } - } - - function depositERC20(address _token, uint256 _amount) - external - { - depositERC20ForUser(_token, msg.sender, _amount); - } - - function depositERC721(address _token, uint256 _tokenId) - external - { - depositERC721ForUser(_token, msg.sender, _tokenId); - } - - function depositBulk(address[] calldata _tokens, uint256[] calldata _amountOrTokens, address _user) - external - { - require( - _tokens.length == _amountOrTokens.length, - "Invalid Input" - ); - uint256 depositId = rootChain.updateDepositId(_tokens.length); - - for (uint256 i = 0; i < _tokens.length; i++) { - // will revert if token is not mapped - if (registry.isTokenMappedAndIsErc721(_tokens[i])) { - IERC721(_tokens[i]).transferFrom(msg.sender, address(this), _amountOrTokens[i]); - } else { + + modifier isPredicateAuthorized() { require( - IERC20(_tokens[i]).transferFrom(msg.sender, address(this), _amountOrTokens[i]), - "TOKEN_TRANSFER_FAILED" + uint8(registry.predicates(msg.sender)) != 0, + "Not a valid predicate" ); - } - _createDepositBlock(_user, _tokens[i], _amountOrTokens[i], depositId); - depositId = depositId.add(1); + _; + } + + // deposit ETH by sending to this contract + function() external payable { + depositEther(); + } + + function transferAssets( + address _token, + address _user, + uint256 _amountOrNFTId + ) external isPredicateAuthorized { + address wethToken = registry.getWethTokenAddress(); + if (registry.isERC721(_token)) { + IERC721(_token).transferFrom(address(this), _user, _amountOrNFTId); + } else if (_token == wethToken) { + WETH t = WETH(_token); + t.withdraw(_amountOrNFTId, _user); + } else { + require( + IERC20(_token).transfer(_user, _amountOrNFTId), + "TRANSFER_FAILED" + ); + } + } + + function depositERC20(address _token, uint256 _amount) external { + depositERC20ForUser(_token, msg.sender, _amount); + } + + function depositERC721(address _token, uint256 _tokenId) external { + depositERC721ForUser(_token, msg.sender, _tokenId); } - } - /** + function depositBulk( + address[] calldata _tokens, + uint256[] calldata _amountOrTokens, + address _user + ) external { + require(_tokens.length == _amountOrTokens.length, "Invalid Input"); + uint256 depositId = rootChain.updateDepositId(_tokens.length); + + for (uint256 i = 0; i < _tokens.length; i++) { + // will revert if token is not mapped + if (registry.isTokenMappedAndIsErc721(_tokens[i])) { + IERC721(_tokens[i]).transferFrom( + msg.sender, + address(this), + _amountOrTokens[i] + ); + } else { + require( + IERC20(_tokens[i]).transferFrom( + msg.sender, + address(this), + _amountOrTokens[i] + ), + "TOKEN_TRANSFER_FAILED" + ); + } + _createDepositBlock( + _user, + _tokens[i], + _amountOrTokens[i], + depositId + ); + depositId = depositId.add(1); + } + } + + /** * @dev Caches childChain and stateSender (frequently used variables) from registry */ - function updateChildChainAndStateSender() public { - (address _childChain, address _stateSender) = registry.getChildChainAndStateSender(); - require( - _stateSender != address(stateSender) || _childChain != childChain, - "Atleast one of stateSender or childChain address should change" - ); - childChain = _childChain; - stateSender = StateSender(_stateSender); - } - - function depositERC20ForUser(address _token, address _user, uint256 _amount) - public - { - require( - IERC20(_token).transferFrom(msg.sender, address(this), _amount), - "TOKEN_TRANSFER_FAILED" - ); - _safeCreateDepositBlock(_user, _token, _amount); - } - - function depositERC721ForUser(address _token, address _user, uint256 _tokenId) - public - { - IERC721(_token).transferFrom(msg.sender, address(this), _tokenId); - _safeCreateDepositBlock(_user, _token, _tokenId); - } - - // @todo: write depositEtherForUser - function depositEther() - public - payable - { - address wethToken = registry.getWethTokenAddress(); - WETH t = WETH(wethToken); - t.deposit.value(msg.value)(); - _safeCreateDepositBlock(msg.sender, wethToken, msg.value); - } - - /** + function updateChildChainAndStateSender() public { + (address _childChain, address _stateSender) = registry + .getChildChainAndStateSender(); + require( + _stateSender != address(stateSender) || _childChain != childChain, + "Atleast one of stateSender or childChain address should change" + ); + childChain = _childChain; + stateSender = StateSender(_stateSender); + } + + function depositERC20ForUser(address _token, address _user, uint256 _amount) + public + { + require( + IERC20(_token).transferFrom(msg.sender, address(this), _amount), + "TOKEN_TRANSFER_FAILED" + ); + _safeCreateDepositBlock(_user, _token, _amount); + } + + function depositERC721ForUser( + address _token, + address _user, + uint256 _tokenId + ) public { + IERC721(_token).transferFrom(msg.sender, address(this), _tokenId); + _safeCreateDepositBlock(_user, _token, _tokenId); + } + + // @todo: write depositEtherForUser + function depositEther() public payable { + address wethToken = registry.getWethTokenAddress(); + WETH t = WETH(wethToken); + t.deposit.value(msg.value)(); + _safeCreateDepositBlock(msg.sender, wethToken, msg.value); + } + + /** * @notice This will be invoked when safeTransferFrom is called on the token contract to deposit tokens to this contract without directly interacting with it * @dev msg.sender is the token contract @@ -139,34 +156,63 @@ contract DepositManager is DepositManagerStorage, IDepositManager, IERC721Receiv * _data Additional data with no specified format * @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` */ - function onERC721Received(address /* _operator */, address _user, uint256 _tokenId, bytes memory /* _data */) - public - returns (bytes4) - { - // the ERC721 contract address is the message sender - _safeCreateDepositBlock(_user, msg.sender, /* token */ _tokenId); - return 0x150b7a02; - } - - // See https://github.com/ethereum/EIPs/issues/223 - function tokenFallback(address _user, uint256 _amount, bytes memory /* _data */) - public - { - _safeCreateDepositBlock(_user, msg.sender, /* token */ _amount); - } - - function _safeCreateDepositBlock(address _user, address _token, uint256 _amountOrToken) - internal - isTokenMapped(_token) - { - _createDepositBlock(_user, _token, _amountOrToken, rootChain.updateDepositId(1) /* returns _depositId */); - } - - function _createDepositBlock(address _user, address _token, uint256 _amountOrToken, uint256 _depositId) - internal - { - deposits[_depositId] = DepositBlock(keccak256(abi.encodePacked(_user, _token, _amountOrToken)), now); - stateSender.syncState(childChain, abi.encode(_user, _token, _amountOrToken, _depositId)); - emit NewDepositBlock(_user, _token, _amountOrToken, _depositId); - } + function onERC721Received( + address, /* _operator */ + address _user, + uint256 _tokenId, + bytes memory /* _data */ + ) public returns (bytes4) { + // the ERC721 contract address is the message sender + _safeCreateDepositBlock( + _user, + msg.sender, + /* token */ + _tokenId + ); + return 0x150b7a02; + } + + // See https://github.com/ethereum/EIPs/issues/223 + function tokenFallback( + address _user, + uint256 _amount, + bytes memory /* _data */ + ) public { + _safeCreateDepositBlock( + _user, + msg.sender, + /* token */ + _amount + ); + } + + function _safeCreateDepositBlock( + address _user, + address _token, + uint256 _amountOrToken + ) internal isTokenMapped(_token) { + _createDepositBlock( + _user, + _token, + _amountOrToken, + rootChain.updateDepositId(1) /* returns _depositId */ + ); + } + + function _createDepositBlock( + address _user, + address _token, + uint256 _amountOrToken, + uint256 _depositId + ) internal { + deposits[_depositId] = DepositBlock( + keccak256(abi.encodePacked(_user, _token, _amountOrToken)), + now + ); + stateSender.syncState( + childChain, + abi.encode(_user, _token, _amountOrToken, _depositId) + ); + emit NewDepositBlock(_user, _token, _amountOrToken, _depositId); + } } diff --git a/contracts/root/depositManager/DepositManagerProxy.sol b/contracts/root/depositManager/DepositManagerProxy.sol index 47abed13d..e5bbe9cbe 100644 --- a/contracts/root/depositManager/DepositManagerProxy.sol +++ b/contracts/root/depositManager/DepositManagerProxy.sol @@ -1,17 +1,16 @@ pragma solidity ^0.5.2; -import { DepositManagerStorage } from "./DepositManagerStorage.sol"; -import { Proxy } from "../../common/misc/Proxy.sol"; -import { Registry } from "../../common/Registry.sol"; -import { RootChain } from "../RootChain.sol"; - +import {DepositManagerStorage} from "./DepositManagerStorage.sol"; +import {Proxy} from "../../common/misc/Proxy.sol"; +import {Registry} from "../../common/Registry.sol"; +import {RootChain} from "../RootChain.sol"; contract DepositManagerProxy is Proxy, DepositManagerStorage { - constructor(address _proxyTo, address _registry, address _rootChain) - public - Proxy(_proxyTo) - { - registry = Registry(_registry); - rootChain = RootChain(_rootChain); - } + constructor(address _proxyTo, address _registry, address _rootChain) + public + Proxy(_proxyTo) + { + registry = Registry(_registry); + rootChain = RootChain(_rootChain); + } } diff --git a/contracts/root/depositManager/DepositManagerStorage.sol b/contracts/root/depositManager/DepositManagerStorage.sol index 2653a212a..e0672d23e 100644 --- a/contracts/root/depositManager/DepositManagerStorage.sol +++ b/contracts/root/depositManager/DepositManagerStorage.sol @@ -1,32 +1,30 @@ pragma solidity ^0.5.2; -import { Registry } from "../../common/Registry.sol"; -import { RootChain } from "../RootChain.sol"; -import { ProxyStorage } from "../../common/misc/ProxyStorage.sol"; -import { StateSender } from "../stateSyncer/StateSender.sol"; - +import {Registry} from "../../common/Registry.sol"; +import {RootChain} from "../RootChain.sol"; +import {ProxyStorage} from "../../common/misc/ProxyStorage.sol"; +import {StateSender} from "../stateSyncer/StateSender.sol"; contract DepositManagerHeader { - event NewDepositBlock( - address indexed owner, - address indexed token, - uint256 amountOrNFTId, - uint256 depositBlockId - ); - - struct DepositBlock { - bytes32 depositHash; - uint256 createdAt; - } + event NewDepositBlock( + address indexed owner, + address indexed token, + uint256 amountOrNFTId, + uint256 depositBlockId + ); + + struct DepositBlock { + bytes32 depositHash; + uint256 createdAt; + } } - contract DepositManagerStorage is ProxyStorage, DepositManagerHeader { - Registry internal registry; - RootChain internal rootChain; - StateSender public stateSender; + Registry internal registry; + RootChain internal rootChain; + StateSender public stateSender; - mapping(uint256 => DepositBlock) public deposits; + mapping(uint256 => DepositBlock) public deposits; - address public childChain; + address public childChain; } diff --git a/contracts/root/depositManager/IDepositManager.sol b/contracts/root/depositManager/IDepositManager.sol index 7cff700fe..3f2aaa5a6 100644 --- a/contracts/root/depositManager/IDepositManager.sol +++ b/contracts/root/depositManager/IDepositManager.sol @@ -1,9 +1,12 @@ pragma solidity ^0.5.2; - interface IDepositManager { - function depositEther() external payable; - function transferAssets(address _token, address _user, uint256 _amountOrNFTId) external; - function depositERC20(address _token, uint256 _amount) external; - function depositERC721(address _token, uint256 _tokenId) external; + function depositEther() external payable; + function transferAssets( + address _token, + address _user, + uint256 _amountOrNFTId + ) external; + function depositERC20(address _token, uint256 _amount) external; + function depositERC721(address _token, uint256 _tokenId) external; } diff --git a/contracts/root/predicates/ERC20Predicate.sol b/contracts/root/predicates/ERC20Predicate.sol index 54de2670c..27359140f 100644 --- a/contracts/root/predicates/ERC20Predicate.sol +++ b/contracts/root/predicates/ERC20Predicate.sol @@ -1,76 +1,88 @@ pragma solidity ^0.5.2; -import { BytesLib } from "../../common/lib/BytesLib.sol"; -import { Common } from "../../common/lib/Common.sol"; -import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; -import { RLPEncode } from "../../common/lib/RLPEncode.sol"; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import {BytesLib} from "../../common/lib/BytesLib.sol"; +import {Common} from "../../common/lib/Common.sol"; +import {Math} from "openzeppelin-solidity/contracts/math/Math.sol"; +import {RLPEncode} from "../../common/lib/RLPEncode.sol"; +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +import {IErcPredicate} from "./IPredicate.sol"; +import {Registry} from "../../common/Registry.sol"; +import { + WithdrawManagerHeader +} from "../withdrawManager/WithdrawManagerStorage.sol"; -import { IErcPredicate } from "./IPredicate.sol"; -import { Registry } from "../../common/Registry.sol"; -import { WithdrawManagerHeader } from "../withdrawManager/WithdrawManagerStorage.sol"; +contract ERC20Predicate is IErcPredicate { + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + using SafeMath for uint256; + + // keccak256('Deposit(address,address,uint256,uint256,uint256)') + bytes32 constant DEPOSIT_EVENT_SIG = 0x4e2ca0515ed1aef1395f66b5303bb5d6f1bf9d61a353fa53f73f8ac9973fa9f6; + // keccak256('Withdraw(address,address,uint256,uint256,uint256)') + bytes32 constant WITHDRAW_EVENT_SIG = 0xebff2602b3f468259e1e99f613fed6691f3a6526effe6ef3e768ba7ae7a36c4f; + // keccak256('LogTransfer(address,address,address,uint256,uint256,uint256,uint256,uint256)') + bytes32 constant LOG_TRANSFER_EVENT_SIG = 0xe6497e3ee548a3372136af2fcb0696db31fc6cf20260707645068bd3fe97f3c4; + // keccak256('LogFeeTransfer(address,address,address,uint256,uint256,uint256,uint256,uint256)') + bytes32 constant LOG_FEE_TRANSFER_EVENT_SIG = 0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63; + + // keccak256('withdraw(uint256)').slice(0, 4) + bytes4 constant WITHDRAW_FUNC_SIG = 0x2e1a7d4d; + // keccak256('transfer(address,uint256)').slice(0, 4) + bytes4 constant TRANSFER_FUNC_SIG = 0xa9059cbb; + + Registry registry; + + constructor( + address _withdrawManager, + address _depositManager, + address _registry + ) public IErcPredicate(_withdrawManager, _depositManager) { + registry = Registry(_registry); + } + function startExitWithBurntTokens(bytes calldata data) external { + RLPReader.RLPItem[] memory referenceTxData = data.toRlpItem().toList(); + bytes memory receipt = referenceTxData[6].toBytes(); + RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); + uint256 logIndex = referenceTxData[9].toUint(); + require(logIndex < MAX_LOGS, "Supporting a max of 10 logs"); + uint256 age = withdrawManager.verifyInclusion( + data, + 0, /* offset */ + false /* verifyTxInclusion */ + ); + inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex + + // "address" (contract address that emitted the log) field in the receipt + address childToken = RLPReader.toAddress(inputItems[0]); + bytes memory logData = inputItems[2].toBytes(); + inputItems = inputItems[1].toList(); // topics + // now, inputItems[i] refers to i-th (0-based) topic in the topics array + // event Withdraw(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) + require( + bytes32(inputItems[0].toUint()) == WITHDRAW_EVENT_SIG, + "Not a withdraw event signature" + ); + address rootToken = address(RLPReader.toUint(inputItems[1])); + require( + msg.sender == address(inputItems[2].toUint()), // from + "Withdrawer and burn exit tx do not match" + ); + uint256 exitAmount = BytesLib.toUint(logData, 0); // amountOrTokenId + withdrawManager.addExitToQueue( + msg.sender, + childToken, + rootToken, + exitAmount, + bytes32(0x0), + true, /* isRegularExit */ + age << 1 + ); + } -contract ERC20Predicate is IErcPredicate { - using RLPReader for bytes; - using RLPReader for RLPReader.RLPItem; - using SafeMath for uint256; - - // keccak256('Deposit(address,address,uint256,uint256,uint256)') - bytes32 constant DEPOSIT_EVENT_SIG = 0x4e2ca0515ed1aef1395f66b5303bb5d6f1bf9d61a353fa53f73f8ac9973fa9f6; - // keccak256('Withdraw(address,address,uint256,uint256,uint256)') - bytes32 constant WITHDRAW_EVENT_SIG = 0xebff2602b3f468259e1e99f613fed6691f3a6526effe6ef3e768ba7ae7a36c4f; - // keccak256('LogTransfer(address,address,address,uint256,uint256,uint256,uint256,uint256)') - bytes32 constant LOG_TRANSFER_EVENT_SIG = 0xe6497e3ee548a3372136af2fcb0696db31fc6cf20260707645068bd3fe97f3c4; - // keccak256('LogFeeTransfer(address,address,address,uint256,uint256,uint256,uint256,uint256)') - bytes32 constant LOG_FEE_TRANSFER_EVENT_SIG = 0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63; - - // keccak256('withdraw(uint256)').slice(0, 4) - bytes4 constant WITHDRAW_FUNC_SIG = 0x2e1a7d4d; - // keccak256('transfer(address,uint256)').slice(0, 4) - bytes4 constant TRANSFER_FUNC_SIG = 0xa9059cbb; - - Registry registry; - - constructor(address _withdrawManager, address _depositManager, address _registry) - IErcPredicate(_withdrawManager, _depositManager) - public - { - registry = Registry(_registry); - } - - function startExitWithBurntTokens(bytes calldata data) - external - { - RLPReader.RLPItem[] memory referenceTxData = data.toRlpItem().toList(); - bytes memory receipt = referenceTxData[6].toBytes(); - RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); - uint256 logIndex = referenceTxData[9].toUint(); - require(logIndex < MAX_LOGS, "Supporting a max of 10 logs"); - uint256 age = withdrawManager.verifyInclusion(data, 0 /* offset */, false /* verifyTxInclusion */); - inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex - - // "address" (contract address that emitted the log) field in the receipt - address childToken = RLPReader.toAddress(inputItems[0]); - bytes memory logData = inputItems[2].toBytes(); - inputItems = inputItems[1].toList(); // topics - // now, inputItems[i] refers to i-th (0-based) topic in the topics array - // event Withdraw(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) - require( - bytes32(inputItems[0].toUint()) == WITHDRAW_EVENT_SIG, - "Not a withdraw event signature" - ); - address rootToken = address(RLPReader.toUint(inputItems[1])); - require( - msg.sender == address(inputItems[2].toUint()), // from - "Withdrawer and burn exit tx do not match" - ); - uint256 exitAmount = BytesLib.toUint(logData, 0); // amountOrTokenId - withdrawManager.addExitToQueue(msg.sender, childToken, rootToken, exitAmount, bytes32(0x0), true /* isRegularExit */, age << 1); - } - - /** + /** * @notice Start an exit by referencing the preceding (reference) transaction * @param data RLP encoded data of the reference tx (proof-of-funds of exitor) that encodes the following fields * headerNumber Header block number of which the reference tx was a part of @@ -87,60 +99,83 @@ contract ERC20Predicate is IErcPredicate { * @return address rootToken that the exit corresponds to * @return uint256 exitAmount */ - function startExitForOutgoingErc20Transfer(bytes calldata data, bytes calldata exitTx) - external - payable - isBondProvided - returns(address /* rootToken */, uint256 /* exitAmount */) - { - // referenceTx is a proof-of-funds of the party who signed the exit tx - // If the exitor is exiting with outgoing transfer, it will refer to their own preceding tx - // If the exitor is exiting with incoming transfer, it will refer to the counterparty's preceding tx - RLPReader.RLPItem[] memory referenceTx = data.toRlpItem().toList(); - - // Validate the exitTx - This may be an in-flight tx, so inclusion will not be checked - ExitTxData memory exitTxData = processExitTx(exitTx); - require( - exitTxData.signer == msg.sender, - "Should be an outgoing transfer" - ); - - // Process the receipt of the referenced tx - ReferenceTxData memory referenceTxData = processReferenceTx( - referenceTx[6].toBytes(), // receipt - referenceTx[9].toUint(), // logIndex - msg.sender, // participant whose proof-of-funds are to be checked in the reference tx - false /* isChallenge */ - ); - require( - exitTxData.childToken == referenceTxData.childToken, - "Reference and exit tx do not correspond to the same child token" - ); - // exitTxData.amountOrToken represents the total exit amount based on the in-flight exit type - // re-using the variable here to avoid stack overflow - exitTxData.amountOrToken = validateSequential(exitTxData, referenceTxData); - - // Checking the inclusion of the receipt of the preceding tx is enough - // It is inconclusive to check the inclusion of the signed tx, hence verifyTxInclusion = false - // age is a measure of the position of the tx in the side chain - referenceTxData.age = withdrawManager.verifyInclusion(data, 0 /* offset */, false /* verifyTxInclusion */) - .add(referenceTxData.age); // Add the logIndex and oIndex from the receipt - - sendBond(); // send BOND_AMOUNT to withdrawManager - - // last bit is to differentiate whether the sender or receiver of the in-flight tx is starting an exit - uint256 exitId = referenceTxData.age << 1; - exitId |= 1; // since msg.sender == exitTxData.signer - withdrawManager.addExitToQueue( - msg.sender, referenceTxData.childToken, referenceTxData.rootToken, - exitTxData.amountOrToken, exitTxData.txHash, false /* isRegularExit */, - exitId - ); - withdrawManager.addInput(exitId, referenceTxData.age, msg.sender, referenceTxData.rootToken); - return (referenceTxData.rootToken, exitTxData.amountOrToken); - } - - /** + function startExitForOutgoingErc20Transfer( + bytes calldata data, + bytes calldata exitTx + ) + external + payable + isBondProvided + returns ( + address, /* rootToken */ + uint256 /* exitAmount */ + ) + { + // referenceTx is a proof-of-funds of the party who signed the exit tx + // If the exitor is exiting with outgoing transfer, it will refer to their own preceding tx + // If the exitor is exiting with incoming transfer, it will refer to the counterparty's preceding tx + RLPReader.RLPItem[] memory referenceTx = data.toRlpItem().toList(); + + // Validate the exitTx - This may be an in-flight tx, so inclusion will not be checked + ExitTxData memory exitTxData = processExitTx(exitTx); + require( + exitTxData.signer == msg.sender, + "Should be an outgoing transfer" + ); + + // Process the receipt of the referenced tx + ReferenceTxData memory referenceTxData = processReferenceTx( + referenceTx[6].toBytes(), // receipt + referenceTx[9].toUint(), // logIndex + msg.sender, // participant whose proof-of-funds are to be checked in the reference tx + false /* isChallenge */ + ); + require( + exitTxData.childToken == referenceTxData.childToken, + "Reference and exit tx do not correspond to the same child token" + ); + // exitTxData.amountOrToken represents the total exit amount based on the in-flight exit type + // re-using the variable here to avoid stack overflow + exitTxData.amountOrToken = validateSequential( + exitTxData, + referenceTxData + ); + + // Checking the inclusion of the receipt of the preceding tx is enough + // It is inconclusive to check the inclusion of the signed tx, hence verifyTxInclusion = false + // age is a measure of the position of the tx in the side chain + referenceTxData.age = withdrawManager + .verifyInclusion( + data, + 0, /* offset */ + false /* verifyTxInclusion */ + ) + .add(referenceTxData.age); // Add the logIndex and oIndex from the receipt + + sendBond(); // send BOND_AMOUNT to withdrawManager + + // last bit is to differentiate whether the sender or receiver of the in-flight tx is starting an exit + uint256 exitId = referenceTxData.age << 1; + exitId |= 1; // since msg.sender == exitTxData.signer + withdrawManager.addExitToQueue( + msg.sender, + referenceTxData.childToken, + referenceTxData.rootToken, + exitTxData.amountOrToken, + exitTxData.txHash, + false, /* isRegularExit */ + exitId + ); + withdrawManager.addInput( + exitId, + referenceTxData.age, + msg.sender, + referenceTxData.rootToken + ); + return (referenceTxData.rootToken, exitTxData.amountOrToken); + } + + /** * @notice Start an exit by referencing the preceding (reference) transaction * @param data RLP encoded data of the reference tx(s) that encodes the following fields for each tx * headerNumber Header block number of which the reference tx was a part of @@ -157,81 +192,118 @@ contract ERC20Predicate is IErcPredicate { * @return address rootToken that the exit corresponds to * @return uint256 exitAmount */ - function startExitForIncomingErc20Transfer(bytes calldata data, bytes calldata exitTx) - external - payable - isBondProvided - returns(address /* rootToken */, uint256 /* exitAmount */) - { - // referenceTx is a proof-of-funds of the party who signed the exit tx - // If the exitor is exiting with outgoing transfer, it will refer to their own preceding tx - // If the exitor is exiting with incoming transfer, it will refer to the counterparty's preceding tx - RLPReader.RLPItem[] memory referenceTx = data.toRlpItem().toList(); - - // Validate the exitTx - This may be an in-flight tx, so inclusion will not be checked - ExitTxData memory exitTxData = processExitTx(exitTx); - require( - exitTxData.signer != msg.sender, - "Should be an incoming transfer" - ); - // Process the receipt (i.e. proof-of-funds of the counterparty) of the referenced tx - ReferenceTxData memory referenceTxData = processReferenceTx( - referenceTx[6].toBytes(), // receipt - referenceTx[9].toUint(), // logIndex - exitTxData.signer, - false /* isChallenge */ - ); - require( - exitTxData.childToken == referenceTxData.childToken, - "Reference and exit tx do not correspond to the same child token" - ); - exitTxData.amountOrToken = validateSequential(exitTxData, referenceTxData); - - // Checking the inclusion of the receipt of the preceding tx is enough - // It is inconclusive to check the inclusion of the signed tx, hence verifyTxInclusion = false - // age is a measure of the position of the tx in the side chain - referenceTxData.age = withdrawManager.verifyInclusion(data, 0 /* offset */, false /* verifyTxInclusion */) - .add(referenceTxData.age); // Add the logIndex and oIndex from the receipt - - ReferenceTxData memory _referenceTxData; - // referenceTx.length > 10 means the exitor sent along another input UTXO to the exit tx - // This will be used to exit with the pre-existing balance on the chain along with the couterparty signed exit tx - if (referenceTx.length > 10) { - _referenceTxData = processReferenceTx( - referenceTx[16].toBytes(), // receipt - referenceTx[19].toUint(), // logIndex - msg.sender, // participant - false /* isChallenge */ - ); - require( - _referenceTxData.childToken == referenceTxData.childToken, - "child tokens in the referenced txs do not match" - ); - require( - _referenceTxData.rootToken == referenceTxData.rootToken, - "root tokens in the referenced txs do not match" - ); - _referenceTxData.age = withdrawManager.verifyInclusion(data, 10 /* offset */, false /* verifyTxInclusion */) - .add(_referenceTxData.age); + function startExitForIncomingErc20Transfer( + bytes calldata data, + bytes calldata exitTx + ) + external + payable + isBondProvided + returns ( + address, /* rootToken */ + uint256 /* exitAmount */ + ) + { + // referenceTx is a proof-of-funds of the party who signed the exit tx + // If the exitor is exiting with outgoing transfer, it will refer to their own preceding tx + // If the exitor is exiting with incoming transfer, it will refer to the counterparty's preceding tx + RLPReader.RLPItem[] memory referenceTx = data.toRlpItem().toList(); + + // Validate the exitTx - This may be an in-flight tx, so inclusion will not be checked + ExitTxData memory exitTxData = processExitTx(exitTx); + require( + exitTxData.signer != msg.sender, + "Should be an incoming transfer" + ); + // Process the receipt (i.e. proof-of-funds of the counterparty) of the referenced tx + ReferenceTxData memory referenceTxData = processReferenceTx( + referenceTx[6].toBytes(), // receipt + referenceTx[9].toUint(), // logIndex + exitTxData.signer, + false /* isChallenge */ + ); + require( + exitTxData.childToken == referenceTxData.childToken, + "Reference and exit tx do not correspond to the same child token" + ); + exitTxData.amountOrToken = validateSequential( + exitTxData, + referenceTxData + ); + + // Checking the inclusion of the receipt of the preceding tx is enough + // It is inconclusive to check the inclusion of the signed tx, hence verifyTxInclusion = false + // age is a measure of the position of the tx in the side chain + referenceTxData.age = withdrawManager + .verifyInclusion( + data, + 0, /* offset */ + false /* verifyTxInclusion */ + ) + .add(referenceTxData.age); // Add the logIndex and oIndex from the receipt + + ReferenceTxData memory _referenceTxData; + // referenceTx.length > 10 means the exitor sent along another input UTXO to the exit tx + // This will be used to exit with the pre-existing balance on the chain along with the couterparty signed exit tx + if (referenceTx.length > 10) { + _referenceTxData = processReferenceTx( + referenceTx[16].toBytes(), // receipt + referenceTx[19].toUint(), // logIndex + msg.sender, // participant + false /* isChallenge */ + ); + require( + _referenceTxData.childToken == referenceTxData.childToken, + "child tokens in the referenced txs do not match" + ); + require( + _referenceTxData.rootToken == referenceTxData.rootToken, + "root tokens in the referenced txs do not match" + ); + _referenceTxData.age = withdrawManager + .verifyInclusion( + data, + 10, /* offset */ + false /* verifyTxInclusion */ + ) + .add(_referenceTxData.age); + } + + sendBond(); // send BOND_AMOUNT to withdrawManager + + // last bit is to differentiate whether the sender or receiver of the in-flight tx is starting an exit + uint256 exitId = Math.max(referenceTxData.age, _referenceTxData.age) << + 1; + withdrawManager.addExitToQueue( + msg.sender, + referenceTxData.childToken, + referenceTxData.rootToken, + exitTxData.amountOrToken.add(_referenceTxData.closingBalance), + exitTxData.txHash, + false, /* isRegularExit */ + exitId + ); + withdrawManager.addInput( + exitId, + referenceTxData.age, + exitTxData.signer, + referenceTxData.rootToken + ); + // If exitor did not have pre-exiting balance on the chain => _referenceTxData has default values; + // In that case, the following input acts as a "dummy" input UTXO to challenge token spends by the exitor + withdrawManager.addInput( + exitId, + _referenceTxData.age, + msg.sender, + referenceTxData.rootToken + ); + return ( + referenceTxData.rootToken, + exitTxData.amountOrToken.add(_referenceTxData.closingBalance) + ); } - sendBond(); // send BOND_AMOUNT to withdrawManager - - // last bit is to differentiate whether the sender or receiver of the in-flight tx is starting an exit - uint256 exitId = Math.max(referenceTxData.age, _referenceTxData.age) << 1; - withdrawManager.addExitToQueue( - msg.sender, referenceTxData.childToken, referenceTxData.rootToken, - exitTxData.amountOrToken.add(_referenceTxData.closingBalance), exitTxData.txHash, false /* isRegularExit */, - exitId - ); - withdrawManager.addInput(exitId, referenceTxData.age, exitTxData.signer, referenceTxData.rootToken); - // If exitor did not have pre-exiting balance on the chain => _referenceTxData has default values; - // In that case, the following input acts as a "dummy" input UTXO to challenge token spends by the exitor - withdrawManager.addInput(exitId, _referenceTxData.age, msg.sender, referenceTxData.rootToken); - return (referenceTxData.rootToken, exitTxData.amountOrToken.add(_referenceTxData.closingBalance)); - } - - /** + /** * @notice Verify the deprecation of a state update * @param exit ABI encoded PlasmaExit data * @param inputUtxo ABI encoded Input UTXO data @@ -250,44 +322,57 @@ contract ERC20Predicate is IErcPredicate { * txProof Merkle proof of the challenge tx * @return Whether or not the state is deprecated */ - function verifyDeprecation(bytes calldata exit, bytes calldata inputUtxo, bytes calldata challengeData) - external - returns (bool) - { - PlasmaExit memory _exit = decodeExit(exit); - (uint256 age, address signer,, address childToken) = decodeInputUtxo(inputUtxo); - RLPReader.RLPItem[] memory _challengeData = challengeData.toRlpItem().toList(); - ExitTxData memory challengeTxData = processChallengeTx(_challengeData[10].toBytes()); - require( - challengeTxData.signer == signer, - "Challenge tx not signed by the party who signed the input UTXO to the exit" - ); - require( - _exit.txHash != challengeTxData.txHash, - "Cannot challenge with the exit tx" - ); - - // receipt alone is not enough for a challenge. It is required to check that the challenge tx was included as well - ReferenceTxData memory referenceTxData = processReferenceTx( - _challengeData[6].toBytes(), // receipt - _challengeData[9].toUint(), // logIndex - challengeTxData.signer, - true /* isChallenge */ - ); - referenceTxData.age = withdrawManager.verifyInclusion(challengeData, 0, true /* verifyTxInclusion */) - .add(referenceTxData.age); - require( - referenceTxData.childToken == childToken && challengeTxData.childToken == childToken, - "LogTransferReceipt, challengeTx token and challenged utxo token do not match" - ); - require( - referenceTxData.age > age, - "Age of challenge log in the receipt needs to be more recent than Utxo being challenged" - ); - return true; - } - - /** + function verifyDeprecation( + bytes calldata exit, + bytes calldata inputUtxo, + bytes calldata challengeData + ) external returns (bool) { + PlasmaExit memory _exit = decodeExit(exit); + (uint256 age, address signer, , address childToken) = decodeInputUtxo( + inputUtxo + ); + RLPReader.RLPItem[] memory _challengeData = challengeData + .toRlpItem() + .toList(); + ExitTxData memory challengeTxData = processChallengeTx( + _challengeData[10].toBytes() + ); + require( + challengeTxData.signer == signer, + "Challenge tx not signed by the party who signed the input UTXO to the exit" + ); + require( + _exit.txHash != challengeTxData.txHash, + "Cannot challenge with the exit tx" + ); + + // receipt alone is not enough for a challenge. It is required to check that the challenge tx was included as well + ReferenceTxData memory referenceTxData = processReferenceTx( + _challengeData[6].toBytes(), // receipt + _challengeData[9].toUint(), // logIndex + challengeTxData.signer, + true /* isChallenge */ + ); + referenceTxData.age = withdrawManager + .verifyInclusion( + challengeData, + 0, + true /* verifyTxInclusion */ + ) + .add(referenceTxData.age); + require( + referenceTxData.childToken == childToken && + challengeTxData.childToken == childToken, + "LogTransferReceipt, challengeTx token and challenged utxo token do not match" + ); + require( + referenceTxData.age > age, + "Age of challenge log in the receipt needs to be more recent than Utxo being challenged" + ); + return true; + } + + /** * @notice Parse a ERC20 LogTransfer event in the receipt * @param state abi encoded (data, participant, verifyInclusion) * data is RLP encoded reference tx receipt that encodes the following fields @@ -305,37 +390,54 @@ contract ERC20Predicate is IErcPredicate { * txProof Merkle proof of the challenge tx * @return abi encoded (closingBalance, ageOfUtxo, childToken, rootToken) */ - function interpretStateUpdate(bytes calldata state) - external - view - returns(bytes memory) - { - // isChallenge - Is the state being parsed for a challenge - (bytes memory _data, address participant, bool verifyInclusion, bool isChallenge) = abi.decode(state, (bytes, address, bool, bool)); - RLPReader.RLPItem[] memory referenceTx = _data.toRlpItem().toList(); - bytes memory receipt = referenceTx[6].toBytes(); - uint256 logIndex = referenceTx[9].toUint(); - require(logIndex < MAX_LOGS, "Supporting a max of 10 logs"); - RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); - inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex - ReferenceTxData memory data; - data.childToken = RLPReader.toAddress(inputItems[0]); // "address" (contract address that emitted the log) field in the receipt - bytes memory logData = inputItems[2].toBytes(); - inputItems = inputItems[1].toList(); // topics - data.rootToken = address(RLPReader.toUint(inputItems[1])); - if (isChallenge) { - processChallenge(inputItems, participant); - } else { - (data.closingBalance, data.age) = processStateUpdate(inputItems, logData, participant); + function interpretStateUpdate(bytes calldata state) + external + view + returns (bytes memory) + { + // isChallenge - Is the state being parsed for a challenge + (bytes memory _data, address participant, bool verifyInclusion, bool isChallenge) = abi + .decode(state, (bytes, address, bool, bool)); + RLPReader.RLPItem[] memory referenceTx = _data.toRlpItem().toList(); + bytes memory receipt = referenceTx[6].toBytes(); + uint256 logIndex = referenceTx[9].toUint(); + require(logIndex < MAX_LOGS, "Supporting a max of 10 logs"); + RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); + inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex + ReferenceTxData memory data; + data.childToken = RLPReader.toAddress(inputItems[0]); // "address" (contract address that emitted the log) field in the receipt + bytes memory logData = inputItems[2].toBytes(); + inputItems = inputItems[1].toList(); // topics + data.rootToken = address(RLPReader.toUint(inputItems[1])); + if (isChallenge) { + processChallenge(inputItems, participant); + } else { + (data.closingBalance, data.age) = processStateUpdate( + inputItems, + logData, + participant + ); + } + data.age = data.age.add(logIndex.mul(MAX_LOGS)); + if (verifyInclusion) { + data.age = data.age.add( + withdrawManager.verifyInclusion( + _data, + 0, + false /* verifyTxInclusion */ + ) + ); + } + return + abi.encode( + data.closingBalance, + data.age, + data.childToken, + data.rootToken + ); } - data.age = data.age.add(logIndex.mul(MAX_LOGS)); - if (verifyInclusion) { - data.age = data.age.add(withdrawManager.verifyInclusion(_data, 0, false /* verifyTxInclusion */)); - } - return abi.encode(data.closingBalance, data.age, data.childToken, data.rootToken); - } - /** + /** * @dev Process the reference tx to start a MoreVP style exit * @param receipt Receipt of the reference transaction * @param logIndex Log Index to read from the receipt @@ -343,208 +445,220 @@ contract ERC20Predicate is IErcPredicate { * @param isChallenge Whether it is a challenge or start exit operation * @return ReferenceTxData Parsed reference tx data */ - function processReferenceTx( - bytes memory receipt, - uint256 logIndex, - address participant, - bool isChallenge) - internal - view - returns(ReferenceTxData memory data) - { - require(logIndex < MAX_LOGS, "Supporting a max of 10 logs"); - RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); - inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex - data.childToken = RLPReader.toAddress(inputItems[0]); // "address" (contract address that emitted the log) field in the receipt - bytes memory logData = inputItems[2].toBytes(); - inputItems = inputItems[1].toList(); // topics - // now, inputItems[i] refers to i-th (0-based) topic in the topics array - bytes32 eventSignature = bytes32(inputItems[0].toUint()); // inputItems[0] is the event signature - data.rootToken = address(RLPReader.toUint(inputItems[1])); - - // When child chain is started, since child matic is a genenis contract at 0x1010, - // the root token corresponding to matic is not known, hence child token address is emitted in LogFeeTransfer events. - // Fix that anomaly here - if (eventSignature == LOG_FEE_TRANSFER_EVENT_SIG) { - data.rootToken = registry.childToRootToken(data.rootToken); + function processReferenceTx( + bytes memory receipt, + uint256 logIndex, + address participant, + bool isChallenge + ) internal view returns (ReferenceTxData memory data) { + require(logIndex < MAX_LOGS, "Supporting a max of 10 logs"); + RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); + inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex + data.childToken = RLPReader.toAddress(inputItems[0]); // "address" (contract address that emitted the log) field in the receipt + bytes memory logData = inputItems[2].toBytes(); + inputItems = inputItems[1].toList(); // topics + // now, inputItems[i] refers to i-th (0-based) topic in the topics array + bytes32 eventSignature = bytes32(inputItems[0].toUint()); // inputItems[0] is the event signature + data.rootToken = address(RLPReader.toUint(inputItems[1])); + + // When child chain is started, since child matic is a genenis contract at 0x1010, + // the root token corresponding to matic is not known, hence child token address is emitted in LogFeeTransfer events. + // Fix that anomaly here + if (eventSignature == LOG_FEE_TRANSFER_EVENT_SIG) { + data.rootToken = registry.childToRootToken(data.rootToken); + } + + if (isChallenge) { + processChallenge(inputItems, participant); + } else { + (data.closingBalance, data.age) = processStateUpdate( + inputItems, + logData, + participant + ); + } + // In our construction, we give an incrementing age to every log in a receipt + data.age = data.age.add(logIndex.mul(MAX_LOGS)); } - if (isChallenge) { - processChallenge(inputItems, participant); - } else { - (data.closingBalance, data.age) = processStateUpdate(inputItems, logData, participant); - } - // In our construction, we give an incrementing age to every log in a receipt - data.age = data.age.add(logIndex.mul(MAX_LOGS)); - } - - function validateSequential(ExitTxData memory exitTxData, ReferenceTxData memory referenceTxData) - internal - pure - returns(uint256 exitAmount) - { - // The closing balance of the referenced tx should be >= exit amount in exitTx - require( - referenceTxData.closingBalance >= exitTxData.amountOrToken, - "Exiting with more tokens than referenced" - ); - // @todo Check exitTxData.nonce > referenceTxData.nonce. The issue is that the referenceTx receipt doesn't contain the nonce - - // If exit tx has is an outgoing transfer from exitor's perspective, exit with closingBalance minus sent amount - if (exitTxData.exitType == ExitType.OutgoingTransfer) { - return referenceTxData.closingBalance.sub(exitTxData.amountOrToken); + function validateSequential( + ExitTxData memory exitTxData, + ReferenceTxData memory referenceTxData + ) internal pure returns (uint256 exitAmount) { + // The closing balance of the referenced tx should be >= exit amount in exitTx + require( + referenceTxData.closingBalance >= exitTxData.amountOrToken, + "Exiting with more tokens than referenced" + ); + // @todo Check exitTxData.nonce > referenceTxData.nonce. The issue is that the referenceTx receipt doesn't contain the nonce + + // If exit tx has is an outgoing transfer from exitor's perspective, exit with closingBalance minus sent amount + if (exitTxData.exitType == ExitType.OutgoingTransfer) { + return referenceTxData.closingBalance.sub(exitTxData.amountOrToken); + } + // If exit tx was burnt tx, exit with the entire referenced balance not just that was burnt, since user only gets one chance to exit MoreVP style + if (exitTxData.exitType == ExitType.Burnt) { + return referenceTxData.closingBalance; + } + // If exit tx has is an incoming transfer from exitor's perspective, exit with exitAmount + return exitTxData.amountOrToken; } - // If exit tx was burnt tx, exit with the entire referenced balance not just that was burnt, since user only gets one chance to exit MoreVP style - if (exitTxData.exitType == ExitType.Burnt) { - return referenceTxData.closingBalance; + + function processChallenge( + RLPReader.RLPItem[] memory inputItems, + address participant + ) internal pure { + bytes32 eventSignature = bytes32(inputItems[0].toUint()); + // event Withdraw(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) + // event Log(Fee)Transfer( + // address indexed token, address indexed from, address indexed to, + // uint256 amountOrTokenId, uint256 input1, uint256 input2, uint256 output1, uint256 output2) + require( + eventSignature == WITHDRAW_EVENT_SIG || + eventSignature == LOG_TRANSFER_EVENT_SIG || + eventSignature == LOG_FEE_TRANSFER_EVENT_SIG, + "Log signature doesnt qualify as a valid spend" + ); + require( + participant == address(inputItems[2].toUint()), // from + "participant and referenced tx do not match" + ); + // oIndex is always 0 for the 2 scenarios above, hence not returning it } - // If exit tx has is an incoming transfer from exitor's perspective, exit with exitAmount - return exitTxData.amountOrToken; - } - - function processChallenge( - RLPReader.RLPItem[] memory inputItems, - address participant) - internal - pure - { - bytes32 eventSignature = bytes32(inputItems[0].toUint()); - // event Withdraw(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) - // event Log(Fee)Transfer( - // address indexed token, address indexed from, address indexed to, - // uint256 amountOrTokenId, uint256 input1, uint256 input2, uint256 output1, uint256 output2) - require( - eventSignature == WITHDRAW_EVENT_SIG - || eventSignature == LOG_TRANSFER_EVENT_SIG - || eventSignature == LOG_FEE_TRANSFER_EVENT_SIG, - "Log signature doesnt qualify as a valid spend" - ); - require( - participant == address(inputItems[2].toUint()), // from - "participant and referenced tx do not match" - ); - // oIndex is always 0 for the 2 scenarios above, hence not returning it - } - - /** + + /** * @notice Parse the state update and check if this predicate recognizes it * @param inputItems inputItems[i] refers to i-th (0-based) topic in the topics array in the log * @param logData Data field (unindexed params) in the log */ - function processStateUpdate( - RLPReader.RLPItem[] memory inputItems, - bytes memory logData, - address participant) - internal - pure - returns (uint256 closingBalance, uint256 oIndex) - { - bytes32 eventSignature = bytes32(inputItems[0].toUint()); - if (eventSignature == DEPOSIT_EVENT_SIG || eventSignature == WITHDRAW_EVENT_SIG) { - // event Deposit(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) - // event Withdraw(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) - require( - participant == address(inputItems[2].toUint()), // from - "Withdrawer and referenced tx do not match" - ); - closingBalance = BytesLib.toUint(logData, 64); // output1 - } else if (eventSignature == LOG_TRANSFER_EVENT_SIG || eventSignature == LOG_FEE_TRANSFER_EVENT_SIG) { - // event Log(Fee)Transfer( - // address indexed token, address indexed from, address indexed to, - // uint256 amountOrTokenId, uint256 input1, uint256 input2, uint256 output1, uint256 output2) - if (participant == address(inputItems[2].toUint())) { // A. Participant transferred tokens - closingBalance = BytesLib.toUint(logData, 96); // output1 - } else if (participant == address(inputItems[3].toUint())) { // B. Participant received tokens - closingBalance = BytesLib.toUint(logData, 128); // output2 - oIndex = 1; - } else { - revert("tx / log doesnt concern the participant"); - } - } else { - revert("Exit type not supported"); + function processStateUpdate( + RLPReader.RLPItem[] memory inputItems, + bytes memory logData, + address participant + ) internal pure returns (uint256 closingBalance, uint256 oIndex) { + bytes32 eventSignature = bytes32(inputItems[0].toUint()); + if ( + eventSignature == DEPOSIT_EVENT_SIG || + eventSignature == WITHDRAW_EVENT_SIG + ) { + // event Deposit(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) + // event Withdraw(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) + require( + participant == address(inputItems[2].toUint()), // from + "Withdrawer and referenced tx do not match" + ); + closingBalance = BytesLib.toUint(logData, 64); // output1 + } else if ( + eventSignature == LOG_TRANSFER_EVENT_SIG || + eventSignature == LOG_FEE_TRANSFER_EVENT_SIG + ) { + // event Log(Fee)Transfer( + // address indexed token, address indexed from, address indexed to, + // uint256 amountOrTokenId, uint256 input1, uint256 input2, uint256 output1, uint256 output2) + if (participant == address(inputItems[2].toUint())) { + // A. Participant transferred tokens + closingBalance = BytesLib.toUint(logData, 96); // output1 + } else if (participant == address(inputItems[3].toUint())) { + // B. Participant received tokens + closingBalance = BytesLib.toUint(logData, 128); // output2 + oIndex = 1; + } else { + revert("tx / log doesnt concern the participant"); + } + } else { + revert("Exit type not supported"); + } } - } - /** + /** * @notice Process the transaction to start a MoreVP style exit from * @param exitTx Signed exit transaction */ - function processExitTx(bytes memory exitTx) - internal - view - returns(ExitTxData memory txData) - { - RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); - require(txList.length == 9, "MALFORMED_WITHDRAW_TX"); - txData.childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx - (txData.signer, txData.txHash) = getAddressFromTx(txList); - if (txData.signer == msg.sender) { - // exit tx is signed by exitor - (txData.amountOrToken, txData.exitType) = processExitTxSender(RLPReader.toBytes(txList[5])); - } else { - // exitor is a counterparty in the provided tx - txData.amountOrToken = processExitTxCounterparty(RLPReader.toBytes(txList[5])); - txData.exitType = ExitType.IncomingTransfer; + function processExitTx(bytes memory exitTx) + internal + view + returns (ExitTxData memory txData) + { + RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); + require(txList.length == 9, "MALFORMED_WITHDRAW_TX"); + txData.childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx + (txData.signer, txData.txHash) = getAddressFromTx(txList); + if (txData.signer == msg.sender) { + // exit tx is signed by exitor + (txData.amountOrToken, txData.exitType) = processExitTxSender( + RLPReader.toBytes(txList[5]) + ); + } else { + // exitor is a counterparty in the provided tx + txData.amountOrToken = processExitTxCounterparty( + RLPReader.toBytes(txList[5]) + ); + txData.exitType = ExitType.IncomingTransfer; + } } - } - /** + /** * @notice Process the challenge transaction * @param exitTx Challenge transaction * @return ExitTxData Parsed challenge transaction data */ - function processChallengeTx(bytes memory exitTx) - internal - pure - returns(ExitTxData memory txData) - { - RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); - require(txList.length == 9, "MALFORMED_WITHDRAW_TX"); - txData.childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx - (txData.signer, txData.txHash) = getAddressFromTx(txList); - // during a challenge, the tx signer must be the first party - (txData.amountOrToken,) = processExitTxSender(RLPReader.toBytes(txList[5])); - } - - /** + function processChallengeTx(bytes memory exitTx) + internal + pure + returns (ExitTxData memory txData) + { + RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); + require(txList.length == 9, "MALFORMED_WITHDRAW_TX"); + txData.childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx + (txData.signer, txData.txHash) = getAddressFromTx(txList); + // during a challenge, the tx signer must be the first party + (txData.amountOrToken, ) = processExitTxSender( + RLPReader.toBytes(txList[5]) + ); + } + + /** * @dev Processes transaction from the "signer / sender" perspective * @param txData Transaction input data * @return exitAmount Number of tokens burnt or sent * @return burnt Whether the tokens were burnt */ - function processExitTxSender(bytes memory txData) - internal - pure - returns (uint256 amount, ExitType exitType) - { - bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); - if (funcSig == WITHDRAW_FUNC_SIG) { - amount = BytesLib.toUint(txData, 4); - exitType = ExitType.Burnt; - } else if (funcSig == TRANSFER_FUNC_SIG) { - amount = BytesLib.toUint(txData, 36); - exitType = ExitType.OutgoingTransfer; - } else { - revert("Exit tx type not supported"); + function processExitTxSender(bytes memory txData) + internal + pure + returns (uint256 amount, ExitType exitType) + { + bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); + if (funcSig == WITHDRAW_FUNC_SIG) { + amount = BytesLib.toUint(txData, 4); + exitType = ExitType.Burnt; + } else if (funcSig == TRANSFER_FUNC_SIG) { + amount = BytesLib.toUint(txData, 36); + exitType = ExitType.OutgoingTransfer; + } else { + revert("Exit tx type not supported"); + } } - } - /** + /** * @dev Processes transaction from the "receiver" perspective * @param txData Transaction input data * @return exitAmount Number of tokens received */ - function processExitTxCounterparty(bytes memory txData) - internal - view - returns (uint256 exitAmount) - { - bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); - require(funcSig == TRANSFER_FUNC_SIG, "Only supports exiting from transfer txs"); - require( - msg.sender == address(BytesLib.toUint(txData, 4)), // to - "Exitor should be the receiver in the exit tx" - ); - exitAmount = BytesLib.toUint(txData, 36); // value - } + function processExitTxCounterparty(bytes memory txData) + internal + view + returns (uint256 exitAmount) + { + bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); + require( + funcSig == TRANSFER_FUNC_SIG, + "Only supports exiting from transfer txs" + ); + require( + msg.sender == address(BytesLib.toUint(txData, 4)), // to + "Exitor should be the receiver in the exit tx" + ); + exitAmount = BytesLib.toUint(txData, 36); // value + } } diff --git a/contracts/root/predicates/ERC721Predicate.sol b/contracts/root/predicates/ERC721Predicate.sol index d646ac380..9628ff268 100644 --- a/contracts/root/predicates/ERC721Predicate.sol +++ b/contracts/root/predicates/ERC721Predicate.sol @@ -1,36 +1,36 @@ pragma solidity ^0.5.2; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import { BytesLib } from "../../common/lib/BytesLib.sol"; -import { Common } from "../../common/lib/Common.sol"; -import { RLPEncode } from "../../common/lib/RLPEncode.sol"; - -import { IErcPredicate } from "./IPredicate.sol"; +import {BytesLib} from "../../common/lib/BytesLib.sol"; +import {Common} from "../../common/lib/Common.sol"; +import {RLPEncode} from "../../common/lib/RLPEncode.sol"; +import {IErcPredicate} from "./IPredicate.sol"; contract ERC721Predicate is IErcPredicate { - using RLPReader for bytes; - using RLPReader for RLPReader.RLPItem; - using SafeMath for uint256; + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + using SafeMath for uint256; - // keccak256('Deposit(address,address,uint256)') - bytes32 constant DEPOSIT_EVENT_SIG = 0x5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62; - // keccak256('Withdraw(address,address,uint256)') - bytes32 constant WITHDRAW_EVENT_SIG = 0x9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb; - // keccak256('LogTransfer(address,address,address,uint256)') - bytes32 constant E721_LOG_TRANSFER_EVENT_SIG = 0x6eabe333476233fd382224f233210cb808a7bc4c4de64f9d76628bf63c677b1a; - // keccak256('withdraw(uint256)').slice(0, 4) - bytes4 constant WITHDRAW_FUNC_SIG = 0x2e1a7d4d; - // keccak256('transferFrom(address,address,uint256)').slice(0, 4) - bytes4 constant TRANSFER_FROM_FUNC_SIG = 0x23b872dd; + // keccak256('Deposit(address,address,uint256)') + bytes32 constant DEPOSIT_EVENT_SIG = 0x5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62; + // keccak256('Withdraw(address,address,uint256)') + bytes32 constant WITHDRAW_EVENT_SIG = 0x9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb; + // keccak256('LogTransfer(address,address,address,uint256)') + bytes32 constant E721_LOG_TRANSFER_EVENT_SIG = 0x6eabe333476233fd382224f233210cb808a7bc4c4de64f9d76628bf63c677b1a; + // keccak256('withdraw(uint256)').slice(0, 4) + bytes4 constant WITHDRAW_FUNC_SIG = 0x2e1a7d4d; + // keccak256('transferFrom(address,address,uint256)').slice(0, 4) + bytes4 constant TRANSFER_FROM_FUNC_SIG = 0x23b872dd; - constructor(address _withdrawManager, address _depositManager) - IErcPredicate(_withdrawManager, _depositManager) - public {} + constructor(address _withdrawManager, address _depositManager) + public + IErcPredicate(_withdrawManager, _depositManager) + {} - /** + /** * @notice Verify the deprecation of a state update * @param exit ABI encoded PlasmaExit data * @param inputUtxo ABI encoded Input UTXO data @@ -49,111 +49,142 @@ contract ERC721Predicate is IErcPredicate { * txProof Merkle proof of the challenge tx * @return Whether or not the state is deprecated */ - function verifyDeprecation(bytes calldata exit, bytes calldata inputUtxo, bytes calldata challengeData) - external - returns (bool) - { - PlasmaExit memory _exit = decodeExit(exit); - (uint256 age, address signer,,) = decodeInputUtxo(inputUtxo); - RLPReader.RLPItem[] memory referenceTxData = challengeData.toRlpItem().toList(); - - ExitTxData memory challengeTxData = processChallengeTx(referenceTxData[10].toBytes()); - require( - challengeTxData.signer == signer, - "Challenge tx not signed by the party who signed the input UTXO to the exit" - ); - require( - _exit.token == challengeTxData.childToken, - "Challenge tx token doesnt match with exit token" - ); - require( - _exit.txHash != challengeTxData.txHash, - "Cannot challenge with the exit tx" - ); - require( - _exit.receiptAmountOrNFTId == challengeTxData.amountOrToken, - "tokenId doesn't match" - ); - uint256 ageOfChallengeTx = withdrawManager.verifyInclusion(challengeData, 0, true /* verifyTxInclusion */); - processReferenceTx( - referenceTxData[6].toBytes(), // receipt - referenceTxData[9].toUint(), // logIndex - challengeTxData.signer, - challengeTxData.childToken, - challengeTxData.amountOrToken, - true // isChallenge - ); - return ageOfChallengeTx > age; - } + function verifyDeprecation( + bytes calldata exit, + bytes calldata inputUtxo, + bytes calldata challengeData + ) external returns (bool) { + PlasmaExit memory _exit = decodeExit(exit); + (uint256 age, address signer, , ) = decodeInputUtxo(inputUtxo); + RLPReader.RLPItem[] memory referenceTxData = challengeData + .toRlpItem() + .toList(); - function interpretStateUpdate(bytes calldata state) - external - view - returns (bytes memory b) - { - (bytes memory _data, address participant, bool verifyInclusion, bool isChallenge) = abi.decode(state, (bytes, address, bool, bool)); - RLPReader.RLPItem[] memory referenceTx = _data.toRlpItem().toList(); - bytes memory receipt = referenceTx[6].toBytes(); - uint256 logIndex = referenceTx[9].toUint(); - require(logIndex < MAX_LOGS, "Supporting a max of 10 logs"); - RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); - inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex - ReferenceTxData memory data; - data.childToken = RLPReader.toAddress(inputItems[0]); // "address" (contract address that emitted the log) field in the receipt - bytes memory logData = inputItems[2].toBytes(); - inputItems = inputItems[1].toList(); // topics - data.rootToken = address(RLPReader.toUint(inputItems[1])); - // event LogTransfer(address indexed token, address indexed from, address indexed to, uint256 tokenId); - data.closingBalance = BytesLib.toUint(logData, 0); // first un-indexed param in LogTransfer - if (isChallenge) { - processChallenge(inputItems, participant); - } else { - data.age = processStateUpdate(inputItems, participant); + ExitTxData memory challengeTxData = processChallengeTx( + referenceTxData[10].toBytes() + ); + require( + challengeTxData.signer == signer, + "Challenge tx not signed by the party who signed the input UTXO to the exit" + ); + require( + _exit.token == challengeTxData.childToken, + "Challenge tx token doesnt match with exit token" + ); + require( + _exit.txHash != challengeTxData.txHash, + "Cannot challenge with the exit tx" + ); + require( + _exit.receiptAmountOrNFTId == challengeTxData.amountOrToken, + "tokenId doesn't match" + ); + uint256 ageOfChallengeTx = withdrawManager.verifyInclusion( + challengeData, + 0, + true /* verifyTxInclusion */ + ); + processReferenceTx( + referenceTxData[6].toBytes(), // receipt + referenceTxData[9].toUint(), // logIndex + challengeTxData.signer, + challengeTxData.childToken, + challengeTxData.amountOrToken, + true // isChallenge + ); + return ageOfChallengeTx > age; } - data.age = data.age.add(logIndex.mul(MAX_LOGS)); - if (verifyInclusion) { - data.age = withdrawManager.verifyInclusion(_data, 0, false /* verifyTxInclusion */).add(data.age); + + function interpretStateUpdate(bytes calldata state) + external + view + returns (bytes memory b) + { + (bytes memory _data, address participant, bool verifyInclusion, bool isChallenge) = abi + .decode(state, (bytes, address, bool, bool)); + RLPReader.RLPItem[] memory referenceTx = _data.toRlpItem().toList(); + bytes memory receipt = referenceTx[6].toBytes(); + uint256 logIndex = referenceTx[9].toUint(); + require(logIndex < MAX_LOGS, "Supporting a max of 10 logs"); + RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); + inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex + ReferenceTxData memory data; + data.childToken = RLPReader.toAddress(inputItems[0]); // "address" (contract address that emitted the log) field in the receipt + bytes memory logData = inputItems[2].toBytes(); + inputItems = inputItems[1].toList(); // topics + data.rootToken = address(RLPReader.toUint(inputItems[1])); + // event LogTransfer(address indexed token, address indexed from, address indexed to, uint256 tokenId); + data.closingBalance = BytesLib.toUint(logData, 0); // first un-indexed param in LogTransfer + if (isChallenge) { + processChallenge(inputItems, participant); + } else { + data.age = processStateUpdate(inputItems, participant); + } + data.age = data.age.add(logIndex.mul(MAX_LOGS)); + if (verifyInclusion) { + data.age = withdrawManager + .verifyInclusion( + _data, + 0, + false /* verifyTxInclusion */ + ) + .add(data.age); + } + return + abi.encode( + data.closingBalance, + data.age, + data.childToken, + data.rootToken + ); } - return abi.encode(data.closingBalance, data.age, data.childToken, data.rootToken); - } - function startExitWithBurntTokens(bytes memory data) - public - returns(bytes memory) - { - RLPReader.RLPItem[] memory referenceTxData = data.toRlpItem().toList(); - bytes memory receipt = referenceTxData[6].toBytes(); - RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); - uint256 age = withdrawManager.verifyInclusion(data, 0 /* offset */, false /* verifyTxInclusion */); - uint256 logIndex = referenceTxData[9].toUint(); - require(logIndex < MAX_LOGS, "Supporting a max of 10 logs"); - inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex + function startExitWithBurntTokens(bytes memory data) + public + returns (bytes memory) + { + RLPReader.RLPItem[] memory referenceTxData = data.toRlpItem().toList(); + bytes memory receipt = referenceTxData[6].toBytes(); + RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); + uint256 age = withdrawManager.verifyInclusion( + data, + 0, /* offset */ + false /* verifyTxInclusion */ + ); + uint256 logIndex = referenceTxData[9].toUint(); + require(logIndex < MAX_LOGS, "Supporting a max of 10 logs"); + inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex - // "address" (contract address that emitted the log) field in the receipt - address childToken = RLPReader.toAddress(inputItems[0]); - bytes memory logData = inputItems[2].toBytes(); - inputItems = inputItems[1].toList(); // topics - // now, inputItems[i] refers to i-th (0-based) topic in the topics array - // event Withdraw(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) - require( - bytes32(inputItems[0].toUint()) == WITHDRAW_EVENT_SIG, - "Not a withdraw event signature" - ); - address rootToken = address(RLPReader.toUint(inputItems[1])); - require( - msg.sender == address(inputItems[2].toUint()), // from - "Withdrawer and burn exit tx do not match" - ); - uint256 tokenId = BytesLib.toUint(logData, 0); - uint256 exitId = age << 1; // last bit is reserved for housekeeping in erc20Predicate - withdrawManager.addExitToQueue( - msg.sender, childToken, rootToken, - tokenId, bytes32(0x0) /* txHash */, true /* isRegularExit */, exitId - ); - return abi.encode(rootToken, tokenId, childToken, exitId); - } + // "address" (contract address that emitted the log) field in the receipt + address childToken = RLPReader.toAddress(inputItems[0]); + bytes memory logData = inputItems[2].toBytes(); + inputItems = inputItems[1].toList(); // topics + // now, inputItems[i] refers to i-th (0-based) topic in the topics array + // event Withdraw(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) + require( + bytes32(inputItems[0].toUint()) == WITHDRAW_EVENT_SIG, + "Not a withdraw event signature" + ); + address rootToken = address(RLPReader.toUint(inputItems[1])); + require( + msg.sender == address(inputItems[2].toUint()), // from + "Withdrawer and burn exit tx do not match" + ); + uint256 tokenId = BytesLib.toUint(logData, 0); + uint256 exitId = age << 1; // last bit is reserved for housekeeping in erc20Predicate + withdrawManager.addExitToQueue( + msg.sender, + childToken, + rootToken, + tokenId, + bytes32(0x0), /* txHash */ + true, /* isRegularExit */ + exitId + ); + return abi.encode(rootToken, tokenId, childToken, exitId); + } - /** + /** * @notice Start an exit by referencing the preceding (reference) transaction * @param data RLP encoded data of the reference tx(s) that encodes the following fields for each tx * headerNumber Header block number of which the reference tx was a part of @@ -173,223 +204,247 @@ contract ERC721Predicate is IErcPredicate { * address childToken: Child token that the exit corresponds to * uint256 exitId */ - function startExit(bytes memory data, bytes memory exitTx) - public - payable - isBondProvided - returns(bytes memory) - { - // referenceTx is a proof-of-funds of the party who signed the exit tx - RLPReader.RLPItem[] memory referenceTxData = data.toRlpItem().toList(); + function startExit(bytes memory data, bytes memory exitTx) + public + payable + isBondProvided + returns (bytes memory) + { + // referenceTx is a proof-of-funds of the party who signed the exit tx + RLPReader.RLPItem[] memory referenceTxData = data.toRlpItem().toList(); - // Validate the exitTx - This may be an in-flight tx, so inclusion will not be checked - ExitTxData memory exitTxData = processExitTx(exitTx); + // Validate the exitTx - This may be an in-flight tx, so inclusion will not be checked + ExitTxData memory exitTxData = processExitTx(exitTx); - // process the receipt of the referenced tx - (address rootToken, uint256 oIndex) = processReferenceTx( - referenceTxData[6].toBytes(), // receipt - referenceTxData[9].toUint(), // logIndex - exitTxData.signer, - exitTxData.childToken, - exitTxData.amountOrToken, - false // isChallenge - ); + // process the receipt of the referenced tx + (address rootToken, uint256 oIndex) = processReferenceTx( + referenceTxData[6].toBytes(), // receipt + referenceTxData[9].toUint(), // logIndex + exitTxData.signer, + exitTxData.childToken, + exitTxData.amountOrToken, + false // isChallenge + ); - sendBond(); // send BOND_AMOUNT to withdrawManager + sendBond(); // send BOND_AMOUNT to withdrawManager - // verifyInclusion returns the position of the receipt in child chain - uint256 ageOfUtxo = withdrawManager.verifyInclusion(data, 0 /* offset */, false /* verifyTxInclusion */) - .add(referenceTxData[9].toUint().mul(MAX_LOGS)) // logIndex * MAX_LOGS - .add(oIndex); // whether exitTxData.signer is a sender or receiver in the referenced receipt - uint256 exitId = ageOfUtxo << 1; // last bit is reserved for housekeeping in erc20Predicate - withdrawManager.addExitToQueue( - msg.sender, - exitTxData.childToken, - rootToken, - exitTxData.amountOrToken, - exitTxData.txHash, - false /* isRegularExit */, - exitId - ); + // verifyInclusion returns the position of the receipt in child chain + uint256 ageOfUtxo = withdrawManager + .verifyInclusion( + data, + 0, /* offset */ + false /* verifyTxInclusion */ + ) + .add(referenceTxData[9].toUint().mul(MAX_LOGS)) // logIndex * MAX_LOGS + .add(oIndex); // whether exitTxData.signer is a sender or receiver in the referenced receipt + uint256 exitId = ageOfUtxo << 1; // last bit is reserved for housekeeping in erc20Predicate + withdrawManager.addExitToQueue( + msg.sender, + exitTxData.childToken, + rootToken, + exitTxData.amountOrToken, + exitTxData.txHash, + false, /* isRegularExit */ + exitId + ); - withdrawManager.addInput(exitId, ageOfUtxo, exitTxData.signer, rootToken); - // Adding a dummy input, owner being the exitor to challenge spends that the exitor made after the transaction being exited from - withdrawManager.addInput(exitId, ageOfUtxo.sub(1), msg.sender, rootToken); - return abi.encode(rootToken, exitTxData.amountOrToken, exitTxData.childToken, exitId); - } + withdrawManager.addInput( + exitId, + ageOfUtxo, + exitTxData.signer, + rootToken + ); + // Adding a dummy input, owner being the exitor to challenge spends that the exitor made after the transaction being exited from + withdrawManager.addInput( + exitId, + ageOfUtxo.sub(1), + msg.sender, + rootToken + ); + return + abi.encode( + rootToken, + exitTxData.amountOrToken, + exitTxData.childToken, + exitId + ); + } - /** + /** * @notice Process the reference tx to start a MoreVP style exit * @param receipt Receipt of the reference transaction * @param logIndex Log Index to read from the receipt * @param participant Either of exitor or a counterparty depending on the type of exit */ - function processReferenceTx( - bytes memory receipt, - uint256 logIndex, - address participant, - address childToken, - uint256 tokenId, - bool isChallenge) - internal - pure - returns(address rootToken, uint256 oIndex) - { - require(logIndex < 10, "Supporting a max of 10 logs"); - RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); - inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex - require( - childToken == RLPReader.toAddress(inputItems[0]), // "address" (contract address that emitted the log) field in the receipt - "Reference and exit tx do not correspond to the same token" - ); - bytes memory logData = inputItems[2].toBytes(); - inputItems = inputItems[1].toList(); // topics - // now, inputItems[i] refers to i-th (0-based) topic in the topics array - // inputItems[0] is the event signature - rootToken = address(RLPReader.toUint(inputItems[1])); - if (isChallenge) { - processChallenge(inputItems, participant); - } else { - oIndex = processStateUpdate(inputItems, participant); + function processReferenceTx( + bytes memory receipt, + uint256 logIndex, + address participant, + address childToken, + uint256 tokenId, + bool isChallenge + ) internal pure returns (address rootToken, uint256 oIndex) { + require(logIndex < 10, "Supporting a max of 10 logs"); + RLPReader.RLPItem[] memory inputItems = receipt.toRlpItem().toList(); + inputItems = inputItems[3].toList()[logIndex].toList(); // select log based on given logIndex + require( + childToken == RLPReader.toAddress(inputItems[0]), // "address" (contract address that emitted the log) field in the receipt + "Reference and exit tx do not correspond to the same token" + ); + bytes memory logData = inputItems[2].toBytes(); + inputItems = inputItems[1].toList(); // topics + // now, inputItems[i] refers to i-th (0-based) topic in the topics array + // inputItems[0] is the event signature + rootToken = address(RLPReader.toUint(inputItems[1])); + if (isChallenge) { + processChallenge(inputItems, participant); + } else { + oIndex = processStateUpdate(inputItems, participant); + } + // tokenId is the first param in logData in all 3 of Deposit, Withdraw and LogTransfer + require( + tokenId == BytesLib.toUint(logData, 0), + "TokenId in the tx and logData do not match" + ); } - // tokenId is the first param in logData in all 3 of Deposit, Withdraw and LogTransfer - require( - tokenId == BytesLib.toUint(logData, 0), - "TokenId in the tx and logData do not match" - ); - } - /** + /** * @notice Parse the state update and check if this predicate recognizes it * @param inputItems inputItems[i] refers to i-th (0-based) topic in the topics array in the log */ - function processStateUpdate( - RLPReader.RLPItem[] memory inputItems, - address participant) - internal - pure - returns (uint256 oIndex) - { - bytes32 eventSignature = bytes32(inputItems[0].toUint()); - address _participant; - if (eventSignature == DEPOSIT_EVENT_SIG) { - // event Deposit(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) - _participant = address(inputItems[2].toUint()); // from - } else if (eventSignature == E721_LOG_TRANSFER_EVENT_SIG) { - // event LogTransfer( - // address indexed token, address indexed from, address indexed to, - // uint256 amountOrTokenId); - // Only makes sense to reference an incoming transfer, unlike erc20 where outgoing transfer also makes sense - _participant = address(inputItems[3].toUint()); // to - oIndex = 1; - } else { - revert("Exit type not supported"); + function processStateUpdate( + RLPReader.RLPItem[] memory inputItems, + address participant + ) internal pure returns (uint256 oIndex) { + bytes32 eventSignature = bytes32(inputItems[0].toUint()); + address _participant; + if (eventSignature == DEPOSIT_EVENT_SIG) { + // event Deposit(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) + _participant = address(inputItems[2].toUint()); // from + } else if (eventSignature == E721_LOG_TRANSFER_EVENT_SIG) { + // event LogTransfer( + // address indexed token, address indexed from, address indexed to, + // uint256 amountOrTokenId); + // Only makes sense to reference an incoming transfer, unlike erc20 where outgoing transfer also makes sense + _participant = address(inputItems[3].toUint()); // to + oIndex = 1; + } else { + revert("Exit type not supported"); + } + require( + participant == _participant, + "tx / log doesnt concern the participant" + ); } - require( - participant == _participant, - "tx / log doesnt concern the participant" - ); - } - /** + /** * @notice Parse the state update and check if this predicate recognizes it * @param inputItems inputItems[i] refers to i-th (0-based) topic in the topics array in the log */ - function processChallenge( - RLPReader.RLPItem[] memory inputItems, - address participant) - internal - pure - { - bytes32 eventSignature = bytes32(inputItems[0].toUint()); - // event Withdraw(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) - // event LogTransfer( - // address indexed token, address indexed from, address indexed to, - // uint256 amountOrTokenId, uint256 input1, uint256 input2, uint256 output1, uint256 output2) - require( - eventSignature == WITHDRAW_EVENT_SIG || eventSignature == E721_LOG_TRANSFER_EVENT_SIG, - "Log signature doesnt qualify as a valid spend" - ); - require( - participant == address(inputItems[2].toUint()), // from - "participant and referenced tx do not match" - ); - // oIndex is always 0 for the 2 scenarios above, hence not returning it - } + function processChallenge( + RLPReader.RLPItem[] memory inputItems, + address participant + ) internal pure { + bytes32 eventSignature = bytes32(inputItems[0].toUint()); + // event Withdraw(address indexed token, address indexed from, uint256 amountOrTokenId, uint256 input1, uint256 output1) + // event LogTransfer( + // address indexed token, address indexed from, address indexed to, + // uint256 amountOrTokenId, uint256 input1, uint256 input2, uint256 output1, uint256 output2) + require( + eventSignature == WITHDRAW_EVENT_SIG || + eventSignature == E721_LOG_TRANSFER_EVENT_SIG, + "Log signature doesnt qualify as a valid spend" + ); + require( + participant == address(inputItems[2].toUint()), // from + "participant and referenced tx do not match" + ); + // oIndex is always 0 for the 2 scenarios above, hence not returning it + } - /** + /** * @notice Process the transaction to start a MoreVP style exit from * @param exitTx Signed exit transaction */ - function processExitTx(bytes memory exitTx) - internal - view - returns(ExitTxData memory txData) - { - RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); - require(txList.length == 9, "MALFORMED_WITHDRAW_TX"); - txData.childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx - (txData.signer, txData.txHash) = getAddressFromTx(txList); - if (txData.signer == msg.sender) { // exit tx is signed by exitor himself - (txData.amountOrToken, txData.exitType) = processExitTxSender(RLPReader.toBytes(txList[5])); - } else { - // exitor is a counterparty in the provided tx - txData.amountOrToken = processExitTxCounterparty(RLPReader.toBytes(txList[5])); - txData.exitType = ExitType.IncomingTransfer; + function processExitTx(bytes memory exitTx) + internal + view + returns (ExitTxData memory txData) + { + RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); + require(txList.length == 9, "MALFORMED_WITHDRAW_TX"); + txData.childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx + (txData.signer, txData.txHash) = getAddressFromTx(txList); + if (txData.signer == msg.sender) { + // exit tx is signed by exitor himself + (txData.amountOrToken, txData.exitType) = processExitTxSender( + RLPReader.toBytes(txList[5]) + ); + } else { + // exitor is a counterparty in the provided tx + txData.amountOrToken = processExitTxCounterparty( + RLPReader.toBytes(txList[5]) + ); + txData.exitType = ExitType.IncomingTransfer; + } } - } - /** + /** * @notice Process the challenge transaction * @param challengeTx Challenge transaction * @return ExitTxData Parsed challenge transaction data */ - function processChallengeTx(bytes memory challengeTx) - internal - pure - returns(ExitTxData memory txData) - { - RLPReader.RLPItem[] memory txList = challengeTx.toRlpItem().toList(); - require(txList.length == 9, "MALFORMED_WITHDRAW_TX"); - txData.childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx - (txData.signer, txData.txHash) = getAddressFromTx(txList); - // during a challenge, the tx signer must be the first party - (txData.amountOrToken,) = processExitTxSender(RLPReader.toBytes(txList[5])); - } + function processChallengeTx(bytes memory challengeTx) + internal + pure + returns (ExitTxData memory txData) + { + RLPReader.RLPItem[] memory txList = challengeTx.toRlpItem().toList(); + require(txList.length == 9, "MALFORMED_WITHDRAW_TX"); + txData.childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx + (txData.signer, txData.txHash) = getAddressFromTx(txList); + // during a challenge, the tx signer must be the first party + (txData.amountOrToken, ) = processExitTxSender( + RLPReader.toBytes(txList[5]) + ); + } - function processExitTxSender(bytes memory txData) - internal - pure - returns (uint256 tokenId, ExitType exitType) - { - bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); - if (funcSig == WITHDRAW_FUNC_SIG) { - // function withdraw(uint256 tokenId) - require(txData.length == 36, "Invalid tx"); // 4 bytes for funcSig and a single bytes32 parameter - tokenId = BytesLib.toUint(txData, 4); - exitType = ExitType.Burnt; - } else if (funcSig == TRANSFER_FROM_FUNC_SIG) { - // function transferFrom(address from, address to, uint256 tokenId) - require(txData.length == 100, "Invalid tx"); // 4 bytes for funcSig and a 3 bytes32 parameters (from, to, tokenId) - tokenId = BytesLib.toUint(txData, 68); - exitType = ExitType.OutgoingTransfer; - } else { - revert("Exit tx type not supported"); + function processExitTxSender(bytes memory txData) + internal + pure + returns (uint256 tokenId, ExitType exitType) + { + bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); + if (funcSig == WITHDRAW_FUNC_SIG) { + // function withdraw(uint256 tokenId) + require(txData.length == 36, "Invalid tx"); // 4 bytes for funcSig and a single bytes32 parameter + tokenId = BytesLib.toUint(txData, 4); + exitType = ExitType.Burnt; + } else if (funcSig == TRANSFER_FROM_FUNC_SIG) { + // function transferFrom(address from, address to, uint256 tokenId) + require(txData.length == 100, "Invalid tx"); // 4 bytes for funcSig and a 3 bytes32 parameters (from, to, tokenId) + tokenId = BytesLib.toUint(txData, 68); + exitType = ExitType.OutgoingTransfer; + } else { + revert("Exit tx type not supported"); + } } - } - function processExitTxCounterparty(bytes memory txData) - internal - view - returns (uint256 tokenId) - { - require(txData.length == 100, "Invalid tx"); // 4 bytes for funcSig and a 2 bytes32 parameters (to, value) - bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); - require(funcSig == TRANSFER_FROM_FUNC_SIG, "Only supports exiting from transfer txs"); - require( - msg.sender == address(BytesLib.toUint(txData, 36)), // to - "Exit tx doesnt concern the exitor" - ); - tokenId = BytesLib.toUint(txData, 68); // NFT ID - } + function processExitTxCounterparty(bytes memory txData) + internal + view + returns (uint256 tokenId) + { + require(txData.length == 100, "Invalid tx"); // 4 bytes for funcSig and a 2 bytes32 parameters (to, value) + bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); + require( + funcSig == TRANSFER_FROM_FUNC_SIG, + "Only supports exiting from transfer txs" + ); + require( + msg.sender == address(BytesLib.toUint(txData, 36)), // to + "Exit tx doesnt concern the exitor" + ); + tokenId = BytesLib.toUint(txData, 68); // NFT ID + } } diff --git a/contracts/root/predicates/IPredicate.sol b/contracts/root/predicates/IPredicate.sol index d9d0d7b94..e84e2a1c7 100644 --- a/contracts/root/predicates/IPredicate.sol +++ b/contracts/root/predicates/IPredicate.sol @@ -1,17 +1,19 @@ pragma solidity ^0.5.2; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; -import { Common } from "../../common/lib/Common.sol"; -import { RLPEncode } from "../../common/lib/RLPEncode.sol"; +import {Common} from "../../common/lib/Common.sol"; +import {RLPEncode} from "../../common/lib/RLPEncode.sol"; -import { IWithdrawManager } from "../withdrawManager/IWithdrawManager.sol"; -import { IDepositManager } from "../depositManager/IDepositManager.sol"; -import { ExitsDataStructure } from "../withdrawManager/WithdrawManagerStorage.sol"; -import { ChainIdMixin } from "../../common/mixin/ChainIdMixin.sol"; +import {IWithdrawManager} from "../withdrawManager/IWithdrawManager.sol"; +import {IDepositManager} from "../depositManager/IDepositManager.sol"; +import { + ExitsDataStructure +} from "../withdrawManager/WithdrawManagerStorage.sol"; +import {ChainIdMixin} from "../../common/mixin/ChainIdMixin.sol"; interface IPredicate { - /** + /** * @notice Verify the deprecation of a state update * @param exit ABI encoded PlasmaExit data * @param inputUtxo ABI encoded Input UTXO data @@ -30,128 +32,138 @@ interface IPredicate { * txProof Merkle proof of the challenge tx * @return Whether or not the state is deprecated */ - function verifyDeprecation(bytes calldata exit, bytes calldata inputUtxo, bytes calldata challengeData) external returns (bool); - - function interpretStateUpdate(bytes calldata state) external view returns (bytes memory); - function onFinalizeExit(bytes calldata data) external; + function verifyDeprecation( + bytes calldata exit, + bytes calldata inputUtxo, + bytes calldata challengeData + ) external returns (bool); + + function interpretStateUpdate(bytes calldata state) + external + view + returns (bytes memory); + function onFinalizeExit(bytes calldata data) external; } - contract PredicateUtils is ExitsDataStructure, ChainIdMixin { - using RLPReader for RLPReader.RLPItem; - - // Bonded exits collaterized at 0.1 ETH - uint256 constant private BOND_AMOUNT = 10 ** 17; - - IWithdrawManager internal withdrawManager; - IDepositManager internal depositManager; - - modifier onlyWithdrawManager() { - require( - msg.sender == address(withdrawManager), - "ONLY_WITHDRAW_MANAGER" - ); - _; - } - - modifier isBondProvided() { - require( - msg.value == BOND_AMOUNT, - "Invalid Bond amount" - ); - _; - } - - function onFinalizeExit(bytes calldata data) - external - onlyWithdrawManager - { - (, address token, address exitor, uint256 tokenId) = decodeExitForProcessExit(data); - depositManager.transferAssets(token, exitor, tokenId); - } - - function sendBond() internal { - address(uint160(address(withdrawManager))).transfer(BOND_AMOUNT); - } - - function getAddressFromTx(RLPReader.RLPItem[] memory txList) - internal - pure - returns (address signer, bytes32 txHash) - { - bytes[] memory rawTx = new bytes[](9); - for (uint8 i = 0; i <= 5; i++) { - rawTx[i] = txList[i].toBytes(); + using RLPReader for RLPReader.RLPItem; + + // Bonded exits collaterized at 0.1 ETH + uint256 private constant BOND_AMOUNT = 10**17; + + IWithdrawManager internal withdrawManager; + IDepositManager internal depositManager; + + modifier onlyWithdrawManager() { + require( + msg.sender == address(withdrawManager), + "ONLY_WITHDRAW_MANAGER" + ); + _; } - rawTx[6] = networkId; - rawTx[7] = hex""; // [7] and [8] have something to do with v, r, s values - rawTx[8] = hex""; - - txHash = keccak256(RLPEncode.encodeList(rawTx)); - signer = ecrecover( - txHash, - Common.getV(txList[6].toBytes(), Common.toUint16(networkId)), - bytes32(txList[7].toUint()), - bytes32(txList[8].toUint()) - ); - } - - function decodeExit(bytes memory data) - internal - pure - returns (PlasmaExit memory) - { - (address owner, - address token, - uint256 amountOrTokenId, - bytes32 txHash, - bool isRegularExit) = abi.decode(data, (address, address, uint256, bytes32, bool)); - return PlasmaExit(amountOrTokenId, txHash, owner, token, isRegularExit, address(0) /* predicate value is not required */); - } - - function decodeExitForProcessExit(bytes memory data) - internal - pure - returns (uint256 exitId, address token, address exitor, uint256 tokenId) - { - (exitId, token, exitor, tokenId) = abi.decode( - data, (uint256, address, address, uint256) - ); - } - - function decodeInputUtxo(bytes memory data) - internal - pure - returns (uint256 age, address signer, address predicate, address token) - { - (age, signer, predicate, token) = abi.decode(data, (uint256, address, address, address)); - } -} + modifier isBondProvided() { + require(msg.value == BOND_AMOUNT, "Invalid Bond amount"); + _; + } + + function onFinalizeExit(bytes calldata data) external onlyWithdrawManager { + (, address token, address exitor, uint256 tokenId) = decodeExitForProcessExit( + data + ); + depositManager.transferAssets(token, exitor, tokenId); + } + + function sendBond() internal { + address(uint160(address(withdrawManager))).transfer(BOND_AMOUNT); + } + + function getAddressFromTx(RLPReader.RLPItem[] memory txList) + internal + pure + returns (address signer, bytes32 txHash) + { + bytes[] memory rawTx = new bytes[](9); + for (uint8 i = 0; i <= 5; i++) { + rawTx[i] = txList[i].toBytes(); + } + rawTx[6] = networkId; + rawTx[7] = hex""; // [7] and [8] have something to do with v, r, s values + rawTx[8] = hex""; + + txHash = keccak256(RLPEncode.encodeList(rawTx)); + signer = ecrecover( + txHash, + Common.getV(txList[6].toBytes(), Common.toUint16(networkId)), + bytes32(txList[7].toUint()), + bytes32(txList[8].toUint()) + ); + } + function decodeExit(bytes memory data) + internal + pure + returns (PlasmaExit memory) + { + (address owner, address token, uint256 amountOrTokenId, bytes32 txHash, bool isRegularExit) = abi + .decode(data, (address, address, uint256, bytes32, bool)); + return + PlasmaExit( + amountOrTokenId, + txHash, + owner, + token, + isRegularExit, + address(0) /* predicate value is not required */ + ); + } + + function decodeExitForProcessExit(bytes memory data) + internal + pure + returns (uint256 exitId, address token, address exitor, uint256 tokenId) + { + (exitId, token, exitor, tokenId) = abi.decode( + data, + (uint256, address, address, uint256) + ); + } + + function decodeInputUtxo(bytes memory data) + internal + pure + returns (uint256 age, address signer, address predicate, address token) + { + (age, signer, predicate, token) = abi.decode( + data, + (uint256, address, address, address) + ); + } + +} contract IErcPredicate is IPredicate, PredicateUtils { - enum ExitType { Invalid, OutgoingTransfer, IncomingTransfer, Burnt } - - struct ExitTxData { - uint256 amountOrToken; - bytes32 txHash; - address childToken; - address signer; - ExitType exitType; - } - - struct ReferenceTxData { - uint256 closingBalance; - uint256 age; - address childToken; - address rootToken; - } - - uint256 constant internal MAX_LOGS = 10; - - constructor(address _withdrawManager, address _depositManager) public { - withdrawManager = IWithdrawManager(_withdrawManager); - depositManager = IDepositManager(_depositManager); - } + enum ExitType {Invalid, OutgoingTransfer, IncomingTransfer, Burnt} + + struct ExitTxData { + uint256 amountOrToken; + bytes32 txHash; + address childToken; + address signer; + ExitType exitType; + } + + struct ReferenceTxData { + uint256 closingBalance; + uint256 age; + address childToken; + address rootToken; + } + + uint256 internal constant MAX_LOGS = 10; + + constructor(address _withdrawManager, address _depositManager) public { + withdrawManager = IWithdrawManager(_withdrawManager); + depositManager = IDepositManager(_depositManager); + } } diff --git a/contracts/root/predicates/MarketplacePredicate.sol b/contracts/root/predicates/MarketplacePredicate.sol index 4a2b7297a..d412420a0 100644 --- a/contracts/root/predicates/MarketplacePredicate.sol +++ b/contracts/root/predicates/MarketplacePredicate.sol @@ -1,71 +1,71 @@ pragma solidity ^0.5.2; -import { BytesLib } from "../../common/lib/BytesLib.sol"; -import { Common } from "../../common/lib/Common.sol"; -import { ECVerify } from "../../common/lib/ECVerify.sol"; -import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; -import { RLPEncode } from "../../common/lib/RLPEncode.sol"; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import {BytesLib} from "../../common/lib/BytesLib.sol"; +import {Common} from "../../common/lib/Common.sol"; +import {ECVerify} from "../../common/lib/ECVerify.sol"; +import {Math} from "openzeppelin-solidity/contracts/math/Math.sol"; +import {RLPEncode} from "../../common/lib/RLPEncode.sol"; +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import { IPredicate, PredicateUtils } from "./IPredicate.sol"; -import { IRootChain } from "../IRootChain.sol"; -import { IWithdrawManager } from "../withdrawManager/IWithdrawManager.sol"; -import { Registry } from "../../common/Registry.sol"; -import { TransferWithSigUtils } from "./TransferWithSigUtils.sol"; +import {IPredicate, PredicateUtils} from "./IPredicate.sol"; +import {IRootChain} from "../IRootChain.sol"; +import {IWithdrawManager} from "../withdrawManager/IWithdrawManager.sol"; +import {Registry} from "../../common/Registry.sol"; +import {TransferWithSigUtils} from "./TransferWithSigUtils.sol"; contract MarketplacePredicate is PredicateUtils { - using RLPReader for bytes; - using RLPReader for RLPReader.RLPItem; - using SafeMath for uint256; + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + using SafeMath for uint256; - // 0xe660b9e4 = keccak256('executeOrder(bytes,bytes,bytes32,uint256,address)').slice(0, 4) - bytes4 constant EXECUTE_ORDER_FUNC_SIG = 0xe660b9e4; + // 0xe660b9e4 = keccak256('executeOrder(bytes,bytes,bytes32,uint256,address)').slice(0, 4) + bytes4 constant EXECUTE_ORDER_FUNC_SIG = 0xe660b9e4; - Registry public registry; - IRootChain public rootChain; + Registry public registry; + IRootChain public rootChain; - struct ExecuteOrderData { - bytes data1; - bytes data2; - bytes32 orderId; - uint256 expiration; - address taker; - } + struct ExecuteOrderData { + bytes data1; + bytes data2; + bytes32 orderId; + uint256 expiration; + address taker; + } - struct Order { - address token; - bytes sig; - uint256 amount; - } + struct Order { + address token; + bytes sig; + uint256 amount; + } - struct ExitTxData { - // token1 and amount1 correspond to what the utxoOwner (tradeParticipant) signed over - uint256 amount1; - uint256 amount2; - address token1; - address token2; - address counterParty; - bytes32 txHash; - uint256 expiration; - } + struct ExitTxData { + // token1 and amount1 correspond to what the utxoOwner (tradeParticipant) signed over + uint256 amount1; + uint256 amount2; + address token1; + address token2; + address counterParty; + bytes32 txHash; + uint256 expiration; + } - struct ReferenceTxData { - uint256 closingBalance; - uint256 age; - address childToken; - address rootToken; - } + struct ReferenceTxData { + uint256 closingBalance; + uint256 age; + address childToken; + address rootToken; + } - constructor(address _rootChain, address _withdrawManager, address _registry) - public - { - withdrawManager = IWithdrawManager(_withdrawManager); - rootChain = IRootChain(_rootChain); - registry = Registry(_registry); - } + constructor(address _rootChain, address _withdrawManager, address _registry) + public + { + withdrawManager = IWithdrawManager(_withdrawManager); + rootChain = IRootChain(_rootChain); + registry = Registry(_registry); + } - /** + /** * @notice Start an exit from in-flight marketplace tx * @param data RLP encoded array of input utxos * data[n] ( 1 < n <= 3) is abi encoded as (predicateAddress, RLP encoded reference tx) @@ -83,111 +83,140 @@ contract MarketplacePredicate is PredicateUtils { * data[2] is the child token that the user wishes to start an exit for * @param exitTx Signed (marketplace.executeOrder) exit transaction */ - function startExit(bytes calldata data, bytes calldata exitTx) - external - payable - isBondProvided - { - RLPReader.RLPItem[] memory referenceTx = data.toRlpItem().toList(); - (address predicate, bytes memory preState) = abi.decode(referenceTx[0].toBytes(), (address, bytes)); - require( - uint8(registry.predicates(predicate)) != 0, - "Not a valid predicate" - ); - ExitTxData memory exitTxData = processExitTx(exitTx, msg.sender); - require( - exitTxData.expiration > rootChain.getLastChildBlock(), - "The inflight exit is not valid, because the marketplace order has expired" - ); + function startExit(bytes calldata data, bytes calldata exitTx) + external + payable + isBondProvided + { + RLPReader.RLPItem[] memory referenceTx = data.toRlpItem().toList(); + (address predicate, bytes memory preState) = abi.decode( + referenceTx[0].toBytes(), + (address, bytes) + ); + require( + uint8(registry.predicates(predicate)) != 0, + "Not a valid predicate" + ); + ExitTxData memory exitTxData = processExitTx(exitTx, msg.sender); + require( + exitTxData.expiration > rootChain.getLastChildBlock(), + "The inflight exit is not valid, because the marketplace order has expired" + ); - // process the first input, which is the proof-of-exitor's funds for token t1 which exitor transferred to counterparty as part of the marketplace tx - ReferenceTxData memory reference1 = processLogTransferReceipt( - predicate, - preState, - msg.sender, - true, /* verifyInclusionInCheckpoint */ - false /* isChallenge */); + // process the first input, which is the proof-of-exitor's funds for token t1 which exitor transferred to counterparty as part of the marketplace tx + ReferenceTxData memory reference1 = processLogTransferReceipt( + predicate, + preState, + msg.sender, + true, /* verifyInclusionInCheckpoint */ + false /* isChallenge */ + ); - validateTokenBalance( - reference1.childToken, - exitTxData.token1, - reference1.closingBalance, - exitTxData.amount1); + validateTokenBalance( + reference1.childToken, + exitTxData.token1, + reference1.closingBalance, + exitTxData.amount1 + ); - // process the second input, which is the proof-of-counterparty's funds for token t2 which the counterparty transferred to exitor as part of the marketplace tx - (predicate, preState) = abi.decode(referenceTx[1].toBytes(), (address, bytes)); - require( - uint8(registry.predicates(predicate)) != 0, - "Not a valid predicate" - ); - ReferenceTxData memory reference2 = processLogTransferReceipt( - predicate, - preState, - exitTxData.counterParty, - true,/*verifyInclusionInCheckpoint*/ - false/* isChallenge */); - validateTokenBalance( - reference2.childToken, - exitTxData.token2, - reference2.closingBalance, - exitTxData.amount2); + // process the second input, which is the proof-of-counterparty's funds for token t2 which the counterparty transferred to exitor as part of the marketplace tx + (predicate, preState) = abi.decode( + referenceTx[1].toBytes(), + (address, bytes) + ); + require( + uint8(registry.predicates(predicate)) != 0, + "Not a valid predicate" + ); + ReferenceTxData memory reference2 = processLogTransferReceipt( + predicate, + preState, + exitTxData.counterParty, + true, /*verifyInclusionInCheckpoint*/ + false /* isChallenge */ + ); + validateTokenBalance( + reference2.childToken, + exitTxData.token2, + reference2.closingBalance, + exitTxData.amount2 + ); - // The last element in referenceTx array refers to the child token being exited - address exitChildToken = address(RLPReader.toUint(referenceTx[referenceTx.length - 1])); - ReferenceTxData memory reference3; - // referenceTx.length == 4 means the exitor sent along another input UTXO for token t2 - // This will be used to exit with the pre-existing balance for token t2 on the chain - // @todo This part is untested - if (referenceTx.length == 4) { - (predicate, preState) = abi.decode(referenceTx[3].toBytes(), (address, bytes)); - reference3 = processLogTransferReceipt( - predicate, - preState, - msg.sender, - true, /* verifyInclusionInCheckpoint */ - false /* isChallenge */); - require( - reference2.childToken == reference3.childToken, - "Child token doesnt match" - ); - } + // The last element in referenceTx array refers to the child token being exited + address exitChildToken = address( + RLPReader.toUint(referenceTx[referenceTx.length - 1]) + ); + ReferenceTxData memory reference3; + // referenceTx.length == 4 means the exitor sent along another input UTXO for token t2 + // This will be used to exit with the pre-existing balance for token t2 on the chain + // @todo This part is untested + if (referenceTx.length == 4) { + (predicate, preState) = abi.decode( + referenceTx[3].toBytes(), + (address, bytes) + ); + reference3 = processLogTransferReceipt( + predicate, + preState, + msg.sender, + true, /* verifyInclusionInCheckpoint */ + false /* isChallenge */ + ); + require( + reference2.childToken == reference3.childToken, + "Child token doesnt match" + ); + } - sendBond(); // send BOND_AMOUNT to withdrawManager + sendBond(); // send BOND_AMOUNT to withdrawManager - // uint256 ageOfYoungestInput = ; - // exitId is the age of the youngest input + a reserved last bit - uint256 exitId = Math.max(Math.max(reference1.age, reference2.age), reference3.age) << 1; - if (exitChildToken == reference1.childToken) { - withdrawManager.addExitToQueue( - msg.sender, exitChildToken, reference1.rootToken, - reference1.closingBalance.sub(exitTxData.amount1), - exitTxData.txHash, false, /* isRegularExit */ - exitId - ); - } else if (exitChildToken == reference2.childToken) { - withdrawManager.addExitToQueue( - msg.sender, exitChildToken, reference2.rootToken, - exitTxData.amount2.add(reference3.closingBalance), - exitTxData.txHash, false,/* isRegularExit */ - exitId - ); + // uint256 ageOfYoungestInput = ; + // exitId is the age of the youngest input + a reserved last bit + uint256 exitId = Math.max( + Math.max(reference1.age, reference2.age), + reference3.age + ) << + 1; + if (exitChildToken == reference1.childToken) { + withdrawManager.addExitToQueue( + msg.sender, + exitChildToken, + reference1.rootToken, + reference1.closingBalance.sub(exitTxData.amount1), + exitTxData.txHash, + false, /* isRegularExit */ + exitId + ); + } else if (exitChildToken == reference2.childToken) { + withdrawManager.addExitToQueue( + msg.sender, + exitChildToken, + reference2.rootToken, + exitTxData.amount2.add(reference3.closingBalance), + exitTxData.txHash, + false, /* isRegularExit */ + exitId + ); + } + // @todo Support batch + withdrawManager.addInput( + exitId, + reference1.age, /* age of input */ + msg.sender, /* party whom this utxo belongs to */ + reference1.rootToken + ); + withdrawManager.addInput( + exitId, + reference2.age, + exitTxData.counterParty, + reference2.rootToken + ); + // If exitor did not have pre=exiting balance on the chain for token t2 + // In that case, the following input acts as a "dummy" input UTXO to challenge token t2 spends by the exitor + withdrawManager.addInput(exitId, 0, msg.sender, reference3.rootToken); } - // @todo Support batch - withdrawManager.addInput(exitId, - reference1.age, /* age of input */ - msg.sender, /* party whom this utxo belongs to */ - reference1.rootToken); - withdrawManager.addInput( - exitId, - reference2.age, - exitTxData.counterParty, - reference2.rootToken); - // If exitor did not have pre=exiting balance on the chain for token t2 - // In that case, the following input acts as a "dummy" input UTXO to challenge token t2 spends by the exitor - withdrawManager.addInput(exitId, 0, msg.sender, reference3.rootToken); - } - /** + /** * @notice Verify the deprecation of a state update * @param exit ABI encoded PlasmaExit data * @param inputUtxo ABI encoded Input UTXO data @@ -206,196 +235,233 @@ contract MarketplacePredicate is PredicateUtils { * txProof Merkle proof of the challenge tx * @return Whether or not the state is deprecated */ - function verifyDeprecation(bytes calldata exit, bytes calldata inputUtxo, bytes calldata challengeData) - external - view - returns (bool) - { - PlasmaExit memory _exit = decodeExit(exit); - (uint256 age, address utxoOwner, address predicate, address childToken) = decodeInputUtxo(inputUtxo); + function verifyDeprecation( + bytes calldata exit, + bytes calldata inputUtxo, + bytes calldata challengeData + ) external view returns (bool) { + PlasmaExit memory _exit = decodeExit(exit); + (uint256 age, address utxoOwner, address predicate, address childToken) = decodeInputUtxo( + inputUtxo + ); - RLPReader.RLPItem[] memory _challengeData = challengeData.toRlpItem().toList(); - ExitTxData memory challengeTxData = processExitTx(_challengeData[10].toBytes(), utxoOwner); + RLPReader.RLPItem[] memory _challengeData = challengeData + .toRlpItem() + .toList(); + ExitTxData memory challengeTxData = processExitTx( + _challengeData[10].toBytes(), + utxoOwner + ); - // receipt alone is not enough for a challenge. It is required to check that the challenge tx was included as well - // Challenge will be considered successful if a more recent LogTransfer event is found - // Interestingly, that will be determined by erc20/721 predicate - ReferenceTxData memory referenceTxData = processLogTransferReceipt( - predicate, - challengeData, - utxoOwner, - true, /* verifyInclusionInCheckpoint */ - true /* isChallenge */); + // receipt alone is not enough for a challenge. It is required to check that the challenge tx was included as well + // Challenge will be considered successful if a more recent LogTransfer event is found + // Interestingly, that will be determined by erc20/721 predicate + ReferenceTxData memory referenceTxData = processLogTransferReceipt( + predicate, + challengeData, + utxoOwner, + true, /* verifyInclusionInCheckpoint */ + true /* isChallenge */ + ); - // this assertion is required only for erc721 because the spend should correspond to the same NFT - if (registry.predicates(predicate) == Registry.Type.ERC721) { - require( - referenceTxData.closingBalance == _exit.receiptAmountOrNFTId && challengeTxData.amount1 == _exit.receiptAmountOrNFTId, - "LogTransferReceipt, challengeTx NFT and challenged utxo NFT do not match" - ); + // this assertion is required only for erc721 because the spend should correspond to the same NFT + if (registry.predicates(predicate) == Registry.Type.ERC721) { + require( + referenceTxData.closingBalance == _exit.receiptAmountOrNFTId && + challengeTxData.amount1 == _exit.receiptAmountOrNFTId, + "LogTransferReceipt, challengeTx NFT and challenged utxo NFT do not match" + ); + } + // assert transferWithSig was still valid when it was included in the child chain + require( + getChildBlockNumberFromAge(referenceTxData.age) <= + challengeTxData.expiration, + "The marketplace order was expired when it was included" + ); + require( + referenceTxData.childToken == childToken && + challengeTxData.token1 == childToken, + "LogTransferReceipt, challengeTx token and challenged utxo token do not match" + ); + require( + challengeTxData.txHash != _exit.txHash, + "Cannot challenge with the exit tx" + ); + require( + referenceTxData.age > age, + "Age of challenge log in the receipt needs to be more recent than Utxo being challenged" + ); + return true; } - // assert transferWithSig was still valid when it was included in the child chain - require( - getChildBlockNumberFromAge(referenceTxData.age) <= challengeTxData.expiration, - "The marketplace order was expired when it was included" - ); - require( - referenceTxData.childToken == childToken && challengeTxData.token1 == childToken, - "LogTransferReceipt, challengeTx token and challenged utxo token do not match" - ); - require( - challengeTxData.txHash != _exit.txHash, - "Cannot challenge with the exit tx" - ); - require( - referenceTxData.age > age, - "Age of challenge log in the receipt needs to be more recent than Utxo being challenged" - ); - return true; - } - function getChildBlockNumberFromAge(uint256 age) internal pure returns(uint256) { - // age is represented as (getExitableAt(createdAt) << 127) | (blockNumber << 32) | branchMask.toRlpItem().toUint(); - return (age << 129) >> 161; - } - - function validateTokenBalance( - address childToken, - address _childToken, - uint256 closingBalance, - uint256 amount) - internal - view - { - require( - childToken == _childToken, - "Child tokens do not match" - ); - if (registry.isChildTokenErc721(childToken)) { - require( - closingBalance == amount, - "Same erc721 token was not referenced" - ); - } else { - require( - closingBalance >= amount, - "Exiting with more tokens than referenced" - ); + function getChildBlockNumberFromAge(uint256 age) + internal + pure + returns (uint256) + { + // age is represented as (getExitableAt(createdAt) << 127) | (blockNumber << 32) | branchMask.toRlpItem().toUint(); + return (age << 129) >> 161; } - } - function processLogTransferReceipt( - address predicate, - bytes memory preState, - address participant, - bool verifyInclusionInCheckpoint, - bool isChallenge) - internal - view - returns(ReferenceTxData memory _referenceTx) - { - bytes memory _preState = IPredicate(predicate).interpretStateUpdate( - abi.encode(preState, participant, - verifyInclusionInCheckpoint, - isChallenge)); - (_referenceTx.closingBalance, - _referenceTx.age, - _referenceTx.childToken, - _referenceTx.rootToken) = abi.decode(_preState, (uint256, uint256, address,address)); - } + function validateTokenBalance( + address childToken, + address _childToken, + uint256 closingBalance, + uint256 amount + ) internal view { + require(childToken == _childToken, "Child tokens do not match"); + if (registry.isChildTokenErc721(childToken)) { + require( + closingBalance == amount, + "Same erc721 token was not referenced" + ); + } else { + require( + closingBalance >= amount, + "Exiting with more tokens than referenced" + ); + } + } - function processExitTx(bytes memory exitTx, address tradeParticipant) - internal - pure - returns(ExitTxData memory txData) - { - RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); - require(txList.length == 9, "MALFORMED_WITHDRAW_TX"); - address marketplaceContract = RLPReader.toAddress(txList[3]); // "to" field in tx - (bytes4 funcSig, ExecuteOrderData memory executeOrder) = decodeExecuteOrder(RLPReader.toBytes(txList[5])); - require( - funcSig == EXECUTE_ORDER_FUNC_SIG, - "Not executeOrder transaction" - ); - txData = verifySignatures(executeOrder, marketplaceContract, tradeParticipant); - (,txData.txHash) = getAddressFromTx(txList); - } + function processLogTransferReceipt( + address predicate, + bytes memory preState, + address participant, + bool verifyInclusionInCheckpoint, + bool isChallenge + ) internal view returns (ReferenceTxData memory _referenceTx) { + bytes memory _preState = IPredicate(predicate).interpretStateUpdate( + abi.encode( + preState, + participant, + verifyInclusionInCheckpoint, + isChallenge + ) + ); + ( + _referenceTx.closingBalance, + _referenceTx.age, + _referenceTx.childToken, + _referenceTx.rootToken + ) = abi.decode(_preState, (uint256, uint256, address, address)); + } - function verifySignatures( - ExecuteOrderData memory executeOrder, - address marketplaceContract, - address tradeParticipant) - internal - pure - returns(ExitTxData memory) - { - Order memory order1 = decodeOrder(executeOrder.data1); - Order memory order2 = decodeOrder(executeOrder.data2); - bytes32 dataHash = TransferWithSigUtils.getTokenTransferOrderHash( - order1.token, // used to evaluate EIP712_DOMAIN_HASH - marketplaceContract, // spender - order1.amount, - keccak256(abi.encodePacked(executeOrder.orderId, order2.token, order2.amount)), - executeOrder.expiration - ); - // Cannot check for deactivated sigs here on the root chain - address tradeParticipant1 = ECVerify.ecrecovery(dataHash, order1.sig); - dataHash = TransferWithSigUtils.getTokenTransferOrderHash( - order2.token, // used to evaluate EIP712_DOMAIN_HASH - marketplaceContract, // spender - order2.amount, - keccak256(abi.encodePacked(executeOrder.orderId, order1.token, order1.amount)), - executeOrder.expiration - ); - // Cannot check for deactivated sigs here on the root chain - address tradeParticipant2 = ECVerify.ecrecovery(dataHash, order2.sig); - require(executeOrder.taker == tradeParticipant2, "Orders are not complimentary"); - // token1 and amount1 in ExitTxData should correspond to what the tradeParticipant signed over (spent in the trade) - if (tradeParticipant1 == tradeParticipant) { - return ExitTxData( - order1.amount, - order2.amount, - order1.token, - order2.token, - tradeParticipant2, - bytes32(0), - executeOrder.expiration); + function processExitTx(bytes memory exitTx, address tradeParticipant) + internal + pure + returns (ExitTxData memory txData) + { + RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); + require(txList.length == 9, "MALFORMED_WITHDRAW_TX"); + address marketplaceContract = RLPReader.toAddress(txList[3]); // "to" field in tx + (bytes4 funcSig, ExecuteOrderData memory executeOrder) = decodeExecuteOrder( + RLPReader.toBytes(txList[5]) + ); + require( + funcSig == EXECUTE_ORDER_FUNC_SIG, + "Not executeOrder transaction" + ); + txData = verifySignatures( + executeOrder, + marketplaceContract, + tradeParticipant + ); + (, txData.txHash) = getAddressFromTx(txList); } - else if (tradeParticipant2 == tradeParticipant) { - return ExitTxData( - order2.amount, - order1.amount, - order2.token, - order1.token, - tradeParticipant1, - bytes32(0), - executeOrder.expiration); + + function verifySignatures( + ExecuteOrderData memory executeOrder, + address marketplaceContract, + address tradeParticipant + ) internal pure returns (ExitTxData memory) { + Order memory order1 = decodeOrder(executeOrder.data1); + Order memory order2 = decodeOrder(executeOrder.data2); + bytes32 dataHash = TransferWithSigUtils.getTokenTransferOrderHash( + order1.token, // used to evaluate EIP712_DOMAIN_HASH + marketplaceContract, // spender + order1.amount, + keccak256( + abi.encodePacked( + executeOrder.orderId, + order2.token, + order2.amount + ) + ), + executeOrder.expiration + ); + // Cannot check for deactivated sigs here on the root chain + address tradeParticipant1 = ECVerify.ecrecovery(dataHash, order1.sig); + dataHash = TransferWithSigUtils.getTokenTransferOrderHash( + order2.token, // used to evaluate EIP712_DOMAIN_HASH + marketplaceContract, // spender + order2.amount, + keccak256( + abi.encodePacked( + executeOrder.orderId, + order1.token, + order1.amount + ) + ), + executeOrder.expiration + ); + // Cannot check for deactivated sigs here on the root chain + address tradeParticipant2 = ECVerify.ecrecovery(dataHash, order2.sig); + require( + executeOrder.taker == tradeParticipant2, + "Orders are not complimentary" + ); + // token1 and amount1 in ExitTxData should correspond to what the tradeParticipant signed over (spent in the trade) + if (tradeParticipant1 == tradeParticipant) { + return + ExitTxData( + order1.amount, + order2.amount, + order1.token, + order2.token, + tradeParticipant2, + bytes32(0), + executeOrder.expiration + ); + } else if (tradeParticipant2 == tradeParticipant) { + return + ExitTxData( + order2.amount, + order1.amount, + order2.token, + order1.token, + tradeParticipant1, + bytes32(0), + executeOrder.expiration + ); + } + revert("Provided tx doesnt concern the exitor (tradeParticipant)"); } - revert("Provided tx doesnt concern the exitor (tradeParticipant)"); - } - function decodeExecuteOrder(bytes memory orderData) - internal - pure - returns (bytes4 funcSig, ExecuteOrderData memory order) - { - funcSig = BytesLib.toBytes4(BytesLib.slice(orderData, 0, 4)); - // 32 + 32 bytes of some (yet to figure out) offset - order.orderId = bytes32(BytesLib.toUint(orderData, 68)); - order.expiration = BytesLib.toUint(orderData, 100); - order.taker = address(BytesLib.toUint(orderData, 132)); - uint256 length = BytesLib.toUint(orderData, 164); - order.data1 = BytesLib.slice(orderData, 196, length); - uint256 offset = 196 + length; - length = BytesLib.toUint(orderData, offset); - order.data2 = BytesLib.slice(orderData, offset + 32, length); - } + function decodeExecuteOrder(bytes memory orderData) + internal + pure + returns (bytes4 funcSig, ExecuteOrderData memory order) + { + funcSig = BytesLib.toBytes4(BytesLib.slice(orderData, 0, 4)); + // 32 + 32 bytes of some (yet to figure out) offset + order.orderId = bytes32(BytesLib.toUint(orderData, 68)); + order.expiration = BytesLib.toUint(orderData, 100); + order.taker = address(BytesLib.toUint(orderData, 132)); + uint256 length = BytesLib.toUint(orderData, 164); + order.data1 = BytesLib.slice(orderData, 196, length); + uint256 offset = 196 + length; + length = BytesLib.toUint(orderData, offset); + order.data2 = BytesLib.slice(orderData, offset + 32, length); + } - function decodeOrder(bytes memory data) - internal - pure - returns (Order memory order) - { - (order.token, order.sig, order.amount) = abi.decode(data, (address, bytes, uint256)); - } + function decodeOrder(bytes memory data) + internal + pure + returns (Order memory order) + { + (order.token, order.sig, order.amount) = abi.decode( + data, + (address, bytes, uint256) + ); + } } diff --git a/contracts/root/predicates/MintableERC721Predicate.sol b/contracts/root/predicates/MintableERC721Predicate.sol index f9e265071..a9777e43f 100644 --- a/contracts/root/predicates/MintableERC721Predicate.sol +++ b/contracts/root/predicates/MintableERC721Predicate.sol @@ -1,212 +1,227 @@ pragma solidity ^0.5.2; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import { BytesLib } from "../../common/lib/BytesLib.sol"; -import { Common } from "../../common/lib/Common.sol"; -import { RLPEncode } from "../../common/lib/RLPEncode.sol"; - -import { ERC721Predicate } from "./ERC721Predicate.sol"; -import { ERC721PlasmaMintable } from "../../common/tokens/ERC721PlasmaMintable.sol"; +import {BytesLib} from "../../common/lib/BytesLib.sol"; +import {Common} from "../../common/lib/Common.sol"; +import {RLPEncode} from "../../common/lib/RLPEncode.sol"; +import {ERC721Predicate} from "./ERC721Predicate.sol"; +import { + ERC721PlasmaMintable +} from "../../common/tokens/ERC721PlasmaMintable.sol"; contract MintableERC721Predicate is ERC721Predicate { - struct MintableTokenInfo { - string uri; - address minter; - bool isVanillaMint; - } + struct MintableTokenInfo { + string uri; + address minter; + bool isVanillaMint; + } - mapping(uint256 => MintableTokenInfo) public exitToMintableTokenInfo; + mapping(uint256 => MintableTokenInfo) public exitToMintableTokenInfo; - constructor(address _withdrawManager, address _depositManager) - ERC721Predicate(_withdrawManager, _depositManager) - public {} + constructor(address _withdrawManager, address _depositManager) + public + ERC721Predicate(_withdrawManager, _depositManager) + {} - /** + /** * @notice Start an exit for a token that was minted and burnt on the side chain * @param data RLP encoded data of the burn tx * @param mintTx Signed mint transaction */ - function startExitForMintableBurntToken(bytes calldata data, bytes calldata mintTx) - external - { - (address rootToken, uint256 tokenId, address childToken, uint256 exitId) = abi.decode( - startExitWithBurntTokens(data), - (address, uint256, address, uint256) - ); - processMint(mintTx, rootToken, tokenId, childToken, exitId); - } - - /** + function startExitForMintableBurntToken( + bytes calldata data, + bytes calldata mintTx + ) external { + (address rootToken, uint256 tokenId, address childToken, uint256 exitId) = abi + .decode( + startExitWithBurntTokens(data), + (address, uint256, address, uint256) + ); + processMint(mintTx, rootToken, tokenId, childToken, exitId); + } + + /** * @notice Start a MoreVP style exit for a token that was minted on the side chain * @param data RLP encoded data of the burn tx * @param mintTx Signed mint transaction */ - function startExitForMintableToken( - bytes calldata data, - bytes calldata mintTx, - bytes calldata exitTx - ) - external - payable - { - (address rootToken, uint256 tokenId, address childToken, uint256 exitId) = abi.decode( - startExit(data, exitTx), - (address, uint256, address, uint256) - ); - processMint(mintTx, rootToken, tokenId, childToken, exitId); - } - - /** + function startExitForMintableToken( + bytes calldata data, + bytes calldata mintTx, + bytes calldata exitTx + ) external payable { + (address rootToken, uint256 tokenId, address childToken, uint256 exitId) = abi + .decode( + startExit(data, exitTx), + (address, uint256, address, uint256) + ); + processMint(mintTx, rootToken, tokenId, childToken, exitId); + } + + /** * @notice Start an exit for a token with metadata that was minted and burnt on the side chain * @param data RLP encoded data of the burn tx * @param mintTx Signed mint transaction */ - function startExitForMetadataMintableBurntToken(bytes calldata data, bytes calldata mintTx) - external - { - (address rootToken, uint256 tokenId, address childToken, uint256 exitId) = abi.decode( - startExitWithBurntTokens(data), - (address, uint256, address, uint256) - ); - processMintWithTokenURI(mintTx, rootToken, tokenId, childToken, exitId); - } - - /** + function startExitForMetadataMintableBurntToken( + bytes calldata data, + bytes calldata mintTx + ) external { + (address rootToken, uint256 tokenId, address childToken, uint256 exitId) = abi + .decode( + startExitWithBurntTokens(data), + (address, uint256, address, uint256) + ); + processMintWithTokenURI(mintTx, rootToken, tokenId, childToken, exitId); + } + + /** * @notice Start a MoreVP style exit for a token with metadata that was minted on the side chain * @param data RLP encoded data of the burn tx * @param mintTx Signed mint transaction * @param exitTx Signed exit transaction */ - function startExitForMetadataMintableToken( - bytes calldata data, - bytes calldata mintTx, - bytes calldata exitTx - ) - external - payable - { - (address rootToken, uint256 tokenId, address childToken, uint256 exitId) = abi.decode( - startExit(data, exitTx), - (address, uint256, address, uint256) - ); - processMintWithTokenURI(mintTx, rootToken, tokenId, childToken, exitId); - } - - function onFinalizeExit(bytes calldata data) - external - onlyWithdrawManager - { - (uint256 exitId, - address token, - address exitor, - uint256 tokenId) = decodeExitForProcessExit(data); - MintableTokenInfo storage info = exitToMintableTokenInfo[exitId]; - - // check that the signer of the mint tx is a valid minter in the root contract - require( - ERC721PlasmaMintable(token).isMinter(info.minter), - "MintableERC721Predicate.processMintWithTokenURI: Not authorized to mint" - ); - - // this predicate contract should have been added to the root token minter role - if (info.isVanillaMint) { - ERC721PlasmaMintable _token = ERC721PlasmaMintable(token); - require( - _token.mint(exitor, tokenId), - "TOKEN_MINT_FAILED" - ); - } else { - ERC721PlasmaMintable _token = ERC721PlasmaMintable(token); - require( - _token.mintWithTokenURI(exitor, tokenId, info.uri), - "MintableERC721Predicate.onFinalizeExit: TOKEN_MINT_FAILED" - ); + function startExitForMetadataMintableToken( + bytes calldata data, + bytes calldata mintTx, + bytes calldata exitTx + ) external payable { + (address rootToken, uint256 tokenId, address childToken, uint256 exitId) = abi + .decode( + startExit(data, exitTx), + (address, uint256, address, uint256) + ); + processMintWithTokenURI(mintTx, rootToken, tokenId, childToken, exitId); + } + + function onFinalizeExit(bytes calldata data) external onlyWithdrawManager { + (uint256 exitId, address token, address exitor, uint256 tokenId) = decodeExitForProcessExit( + data + ); + MintableTokenInfo storage info = exitToMintableTokenInfo[exitId]; + + // check that the signer of the mint tx is a valid minter in the root contract + require( + ERC721PlasmaMintable(token).isMinter(info.minter), + "MintableERC721Predicate.processMintWithTokenURI: Not authorized to mint" + ); + + // this predicate contract should have been added to the root token minter role + if (info.isVanillaMint) { + ERC721PlasmaMintable _token = ERC721PlasmaMintable(token); + require(_token.mint(exitor, tokenId), "TOKEN_MINT_FAILED"); + } else { + ERC721PlasmaMintable _token = ERC721PlasmaMintable(token); + require( + _token.mintWithTokenURI(exitor, tokenId, info.uri), + "MintableERC721Predicate.onFinalizeExit: TOKEN_MINT_FAILED" + ); + } + } + + function processMint( + bytes memory mintTx, + address rootToken, + uint256 tokenId, + address childToken, + uint256 exitId + ) internal { + RLPReader.RLPItem[] memory txList = mintTx.toRlpItem().toList(); + _processRawMint(RLPReader.toBytes(txList[5]), tokenId); + ERC721PlasmaMintable token = ERC721PlasmaMintable(rootToken); + require( + !token.exists(tokenId), + "MintableERC721Predicate.processMint: Token being exited already exists" + ); + + // Will lazily (at the time of processExits) check that the signer of the mint tx is a valid minter in the root contract + (address minter, ) = getAddressFromTx(txList); + exitToMintableTokenInfo[exitId] = MintableTokenInfo( + "", + /* uri */ + minter, + true /* isVanillaMint */ + ); + address _childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx + require( + childToken == _childToken, + "MintableERC721Predicate.processMint; Token in reference and mint tx are not same" + ); + } + + function _processRawMint(bytes memory txData, uint256 tokenId) + internal + pure + { + bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); + require( + funcSig == 0x40c10f19, // keccak256('mint(address,uint256)').slice(0, 4) + "MintableERC721Predicate._processRawMint: funcSig does not match with mint" + ); + uint256 _tokenId; + (, _tokenId) = abi.decode( + BytesLib.slice(txData, 4, txData.length - 4), + (address, uint256) + ); + require( + _tokenId == tokenId, + "MintableERC721Predicate._processRawMint: TokenIds in exit and mint tx do not match" + ); + } + + function processMintWithTokenURI( + bytes memory mintTx, + address rootToken, + uint256 tokenId, + address childToken, + uint256 exitId + ) internal { + ERC721PlasmaMintable token = ERC721PlasmaMintable(rootToken); + require( + !token.exists(tokenId), + "MintableERC721Predicate.processMintWithTokenURI: Token being exited already exists" + ); + + RLPReader.RLPItem[] memory txList = mintTx.toRlpItem().toList(); + string memory uri = _processRawMintWithTokenURI( + RLPReader.toBytes(txList[5]), + tokenId + ); + // Will lazily (at the time of processExits) check that the signer of the mint tx is a valid minter in the root contract + (address minter, ) = getAddressFromTx(txList); + exitToMintableTokenInfo[exitId] = MintableTokenInfo( + uri, + minter, + false /* isVanillaMint */ + ); + address _childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx + require( + childToken == _childToken, + "MintableERC721Predicate.processMintWithTokenURI; Token in reference and mint tx are not same" + ); + } + + function _processRawMintWithTokenURI(bytes memory txData, uint256 tokenId) + internal + pure + returns (string memory uri) + { + bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); + require( + funcSig == 0x50bb4e7f, // keccak256('mintWithTokenURI(address,uint256,string)').slice(0, 4) + "MintableERC721Predicate._processRawMintWithTokenURI: funcSig does not match mintWithTokenURI" + ); + uint256 _tokenId; + (, _tokenId, uri) = abi.decode( + BytesLib.slice(txData, 4, txData.length - 4), + (address, uint256, string) + ); + require( + _tokenId == tokenId, + "MintableERC721Predicate._processRawMintWithTokenURI: TokenIds in exit and mint tx do not match" + ); } - } - - function processMint(bytes memory mintTx, address rootToken, uint256 tokenId, address childToken, uint256 exitId) - internal - { - RLPReader.RLPItem[] memory txList = mintTx.toRlpItem().toList(); - _processRawMint(RLPReader.toBytes(txList[5]), tokenId); - ERC721PlasmaMintable token = ERC721PlasmaMintable(rootToken); - require( - !token.exists(tokenId), - "MintableERC721Predicate.processMint: Token being exited already exists" - ); - - // Will lazily (at the time of processExits) check that the signer of the mint tx is a valid minter in the root contract - (address minter,) = getAddressFromTx(txList); - exitToMintableTokenInfo[exitId] = MintableTokenInfo('',/* uri */ minter, true /* isVanillaMint */); - address _childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx - require( - childToken == _childToken, - "MintableERC721Predicate.processMint; Token in reference and mint tx are not same" - ); - } - - function _processRawMint(bytes memory txData, uint256 tokenId) - internal - pure - { - bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); - require( - funcSig == 0x40c10f19, // keccak256('mint(address,uint256)').slice(0, 4) - "MintableERC721Predicate._processRawMint: funcSig does not match with mint" - ); - uint256 _tokenId; - (,_tokenId) = abi.decode( - BytesLib.slice(txData, 4, txData.length - 4), - (address, uint256) - ); - require( - _tokenId == tokenId, - "MintableERC721Predicate._processRawMint: TokenIds in exit and mint tx do not match" - ); - } - - function processMintWithTokenURI(bytes memory mintTx, address rootToken, uint256 tokenId, address childToken, uint256 exitId) - internal - { - ERC721PlasmaMintable token = ERC721PlasmaMintable(rootToken); - require( - !token.exists(tokenId), - "MintableERC721Predicate.processMintWithTokenURI: Token being exited already exists" - ); - - RLPReader.RLPItem[] memory txList = mintTx.toRlpItem().toList(); - string memory uri = _processRawMintWithTokenURI(RLPReader.toBytes(txList[5]), tokenId); - // Will lazily (at the time of processExits) check that the signer of the mint tx is a valid minter in the root contract - (address minter,) = getAddressFromTx(txList); - exitToMintableTokenInfo[exitId] = MintableTokenInfo(uri, minter, false /* isVanillaMint */); - address _childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx - require( - childToken == _childToken, - "MintableERC721Predicate.processMintWithTokenURI; Token in reference and mint tx are not same" - ); - } - - function _processRawMintWithTokenURI(bytes memory txData, uint256 tokenId) - internal - pure - returns (string memory uri) - { - bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txData, 0, 4)); - require( - funcSig == 0x50bb4e7f, // keccak256('mintWithTokenURI(address,uint256,string)').slice(0, 4) - "MintableERC721Predicate._processRawMintWithTokenURI: funcSig does not match mintWithTokenURI" - ); - uint256 _tokenId; - (,_tokenId, uri) = abi.decode( - BytesLib.slice(txData, 4, txData.length - 4), - (address, uint256, string) - ); - require( - _tokenId == tokenId, - "MintableERC721Predicate._processRawMintWithTokenURI: TokenIds in exit and mint tx do not match" - ); - } } diff --git a/contracts/root/predicates/TransferWithSigPredicate.sol b/contracts/root/predicates/TransferWithSigPredicate.sol index 66fc883cd..168228b06 100644 --- a/contracts/root/predicates/TransferWithSigPredicate.sol +++ b/contracts/root/predicates/TransferWithSigPredicate.sol @@ -1,53 +1,52 @@ pragma solidity ^0.5.2; -import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import {Math} from "openzeppelin-solidity/contracts/math/Math.sol"; +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import { BytesLib } from "../../common/lib/BytesLib.sol"; -import { ECVerify } from "../../common/lib/ECVerify.sol"; - -import { IPredicate, PredicateUtils } from "./IPredicate.sol"; -import { IRootChain } from "../IRootChain.sol"; -import { IWithdrawManager } from "../withdrawManager/IWithdrawManager.sol"; -import { Registry } from "../../common/Registry.sol"; -import { TransferWithSigUtils } from "./TransferWithSigUtils.sol"; +import {BytesLib} from "../../common/lib/BytesLib.sol"; +import {ECVerify} from "../../common/lib/ECVerify.sol"; +import {IPredicate, PredicateUtils} from "./IPredicate.sol"; +import {IRootChain} from "../IRootChain.sol"; +import {IWithdrawManager} from "../withdrawManager/IWithdrawManager.sol"; +import {Registry} from "../../common/Registry.sol"; +import {TransferWithSigUtils} from "./TransferWithSigUtils.sol"; contract TransferWithSigPredicate is PredicateUtils { - using RLPReader for bytes; - using RLPReader for RLPReader.RLPItem; - using SafeMath for uint256; + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + using SafeMath for uint256; - // 0xe660b9e4 = keccak256('transferWithSig(bytes,uint256,bytes32,uint256,address)').slice(0, 4) - bytes4 constant TRANSFER_WITH_SIG_FUNC_SIG = 0x19d27d9c; + // 0xe660b9e4 = keccak256('transferWithSig(bytes,uint256,bytes32,uint256,address)').slice(0, 4) + bytes4 constant TRANSFER_WITH_SIG_FUNC_SIG = 0x19d27d9c; - Registry public registry; - IRootChain public rootChain; + Registry public registry; + IRootChain public rootChain; - struct ReferenceTxData { - uint256 closingBalance; - uint256 age; - address childToken; - address rootToken; - } + struct ReferenceTxData { + uint256 closingBalance; + uint256 age; + address childToken; + address rootToken; + } - struct ExitTxData { - uint256 amountOrToken; - bytes32 txHash; - address childToken; - address signer; - } + struct ExitTxData { + uint256 amountOrToken; + bytes32 txHash; + address childToken; + address signer; + } - constructor(address _rootChain, address _withdrawManager, address _registry) - public - { - withdrawManager = IWithdrawManager(_withdrawManager); - rootChain = IRootChain(_rootChain); - registry = Registry(_registry); - } + constructor(address _rootChain, address _withdrawManager, address _registry) + public + { + withdrawManager = IWithdrawManager(_withdrawManager); + rootChain = IRootChain(_rootChain); + registry = Registry(_registry); + } - /** + /** * @notice Start an exit from in-flight transferWithSig tx while referencing the exitor's pre-existing balance on the chain for the token * @param data RLP encoded array of 1 input utxo (data format is to keep it consistent with other startExit methods) * data[0] should be the exitor's proof-of-funds and encodes the following fields @@ -63,57 +62,64 @@ contract TransferWithSigPredicate is PredicateUtils { * logIndex Log Index to read from the receipt * @param exitTx ERC20 transfer executed using a transferWithSig */ - function startExitForOutgoingErc20Transfer(bytes calldata data, bytes calldata exitTx) - external - payable - isBondProvided - { - RLPReader.RLPItem[] memory referenceTx = data.toRlpItem().toList(); - bytes memory preState = referenceTx[0].toBytes(); - (ExitTxData memory exitTxData, uint256 expiration) = processExitTx(exitTx); - require( - expiration > rootChain.getLastChildBlock(), - "The inflight exit is not valid, because the transfer sig has expired" - ); - require( - exitTxData.signer == msg.sender, - "Should be an outgoing transfer" - ); - address erc20Predicate = registry.erc20Predicate(); - ReferenceTxData memory referenceTxData = processLogTransferReceipt( - erc20Predicate, - preState, - msg.sender, - true, /* verifyInclusionInCheckpoint */ - false /* isChallenge */); - require( - exitTxData.childToken == referenceTxData.childToken, - "Reference and exit tx do not correspond to the same child token" - ); - // The closing balance of the referenced tx should be >= exit amount in exitTx - require( - referenceTxData.closingBalance >= exitTxData.amountOrToken, - "Exiting with more tokens than referenced" - ); + function startExitForOutgoingErc20Transfer( + bytes calldata data, + bytes calldata exitTx + ) external payable isBondProvided { + RLPReader.RLPItem[] memory referenceTx = data.toRlpItem().toList(); + bytes memory preState = referenceTx[0].toBytes(); + (ExitTxData memory exitTxData, uint256 expiration) = processExitTx( + exitTx + ); + require( + expiration > rootChain.getLastChildBlock(), + "The inflight exit is not valid, because the transfer sig has expired" + ); + require( + exitTxData.signer == msg.sender, + "Should be an outgoing transfer" + ); + address erc20Predicate = registry.erc20Predicate(); + ReferenceTxData memory referenceTxData = processLogTransferReceipt( + erc20Predicate, + preState, + msg.sender, + true, /* verifyInclusionInCheckpoint */ + false /* isChallenge */ + ); + require( + exitTxData.childToken == referenceTxData.childToken, + "Reference and exit tx do not correspond to the same child token" + ); + // The closing balance of the referenced tx should be >= exit amount in exitTx + require( + referenceTxData.closingBalance >= exitTxData.amountOrToken, + "Exiting with more tokens than referenced" + ); - sendBond(); // send BOND_AMOUNT to withdrawManager + sendBond(); // send BOND_AMOUNT to withdrawManager - // last bit is to differentiate whether the sender or receiver of the in-flight tx is starting an exit - uint256 exitId = referenceTxData.age << 1; - exitId |= 1; // since msg.sender == exitTxData.signer - withdrawManager.addExitToQueue( - msg.sender, referenceTxData.childToken, referenceTxData.rootToken, - referenceTxData.closingBalance.sub(exitTxData.amountOrToken), exitTxData.txHash, false, /* isRegularExit */ - exitId - ); - withdrawManager.addInput( - exitId, - referenceTxData.age, - msg.sender, - referenceTxData.rootToken); - } + // last bit is to differentiate whether the sender or receiver of the in-flight tx is starting an exit + uint256 exitId = referenceTxData.age << 1; + exitId |= 1; // since msg.sender == exitTxData.signer + withdrawManager.addExitToQueue( + msg.sender, + referenceTxData.childToken, + referenceTxData.rootToken, + referenceTxData.closingBalance.sub(exitTxData.amountOrToken), + exitTxData.txHash, + false, /* isRegularExit */ + exitId + ); + withdrawManager.addInput( + exitId, + referenceTxData.age, + msg.sender, + referenceTxData.rootToken + ); + } - /** + /** * @notice Start an exit from in-flight transferWithSig tx while also referencing the exitor's pre-existing balance on the chain for the token * @param data RLP encoded array of 2 input utxos * data[0] should be the counterparty's proof-of-funds @@ -131,78 +137,94 @@ contract TransferWithSigPredicate is PredicateUtils { * logIndex Log Index to read from the receipt * @param exitTx ERC20 transfer executed using a transferWithSig */ - function startExitForIncomingErc20Transfer(bytes calldata data, bytes calldata exitTx) - external - payable - isBondProvided - { - RLPReader.RLPItem[] memory referenceTx = data.toRlpItem().toList(); - bytes memory preState = referenceTx[0].toBytes(); - (ExitTxData memory exitTxData, uint256 expiration) = processExitTx(exitTx); - require( - expiration > rootChain.getLastChildBlock(), - "The inflight exit is not valid, because the transfer sig has expired" - ); - require( - exitTxData.signer != msg.sender, - "Should be an incoming transfer" - ); - address erc20Predicate = registry.erc20Predicate(); - // Process the receipt (i.e. proof-of-funds of the counterparty) of the referenced tx - ReferenceTxData memory referenceTxData = processLogTransferReceipt( - erc20Predicate, - preState, - exitTxData.signer, - true,/* verifyInclusionInCheckpoint */ - false /* isChallenge */); - require( - exitTxData.childToken == referenceTxData.childToken, - "Reference and exit tx do not correspond to the same child token" - ); - // The closing balance of the referenced tx should be >= exit amount in exitTx - require( - referenceTxData.closingBalance >= exitTxData.amountOrToken, - "Exiting with more tokens than referenced" - ); + function startExitForIncomingErc20Transfer( + bytes calldata data, + bytes calldata exitTx + ) external payable isBondProvided { + RLPReader.RLPItem[] memory referenceTx = data.toRlpItem().toList(); + bytes memory preState = referenceTx[0].toBytes(); + (ExitTxData memory exitTxData, uint256 expiration) = processExitTx( + exitTx + ); + require( + expiration > rootChain.getLastChildBlock(), + "The inflight exit is not valid, because the transfer sig has expired" + ); + require( + exitTxData.signer != msg.sender, + "Should be an incoming transfer" + ); + address erc20Predicate = registry.erc20Predicate(); + // Process the receipt (i.e. proof-of-funds of the counterparty) of the referenced tx + ReferenceTxData memory referenceTxData = processLogTransferReceipt( + erc20Predicate, + preState, + exitTxData.signer, + true, /* verifyInclusionInCheckpoint */ + false /* isChallenge */ + ); + require( + exitTxData.childToken == referenceTxData.childToken, + "Reference and exit tx do not correspond to the same child token" + ); + // The closing balance of the referenced tx should be >= exit amount in exitTx + require( + referenceTxData.closingBalance >= exitTxData.amountOrToken, + "Exiting with more tokens than referenced" + ); - // referenceTx.length == 2 means the exitor sent along another input UTXO to the exit tx - // This will be used to exit with the exitor's pre-existing balance on the chain for the token - ReferenceTxData memory _referenceTxData; - if (referenceTx.length > 1) { - preState = referenceTx[1].toBytes(); - _referenceTxData = processLogTransferReceipt( - erc20Predicate, - preState, - msg.sender, - true, /* verifyInclusionInCheckpoint */ - false /* isChallenge */); - require( - _referenceTxData.childToken == referenceTxData.childToken, - "child tokens in the referenced txs do not match" - ); - require( - _referenceTxData.rootToken == referenceTxData.rootToken, - "root tokens in the referenced txs do not match" - ); - } + // referenceTx.length == 2 means the exitor sent along another input UTXO to the exit tx + // This will be used to exit with the exitor's pre-existing balance on the chain for the token + ReferenceTxData memory _referenceTxData; + if (referenceTx.length > 1) { + preState = referenceTx[1].toBytes(); + _referenceTxData = processLogTransferReceipt( + erc20Predicate, + preState, + msg.sender, + true, /* verifyInclusionInCheckpoint */ + false /* isChallenge */ + ); + require( + _referenceTxData.childToken == referenceTxData.childToken, + "child tokens in the referenced txs do not match" + ); + require( + _referenceTxData.rootToken == referenceTxData.rootToken, + "root tokens in the referenced txs do not match" + ); + } - sendBond(); // send BOND_AMOUNT to withdrawManager + sendBond(); // send BOND_AMOUNT to withdrawManager - // last bit is to differentiate whether the sender or receiver of the in-flight tx is starting an exit - uint256 exitId = Math.max(referenceTxData.age, _referenceTxData.age) << 1; - withdrawManager.addExitToQueue( - msg.sender, referenceTxData.childToken, referenceTxData.rootToken, - exitTxData.amountOrToken.add( - _referenceTxData.closingBalance), - exitTxData.txHash, - false, /* isRegularExit */ - exitId); - withdrawManager.addInput(exitId, referenceTxData.age, exitTxData.signer, referenceTxData.rootToken); - // Even if referenceTx.length == 1, the following input acts as a "dummy" input UTXO to challenge token spends by the exitor - withdrawManager.addInput(exitId, _referenceTxData.age, msg.sender, referenceTxData.rootToken); - } + // last bit is to differentiate whether the sender or receiver of the in-flight tx is starting an exit + uint256 exitId = Math.max(referenceTxData.age, _referenceTxData.age) << + 1; + withdrawManager.addExitToQueue( + msg.sender, + referenceTxData.childToken, + referenceTxData.rootToken, + exitTxData.amountOrToken.add(_referenceTxData.closingBalance), + exitTxData.txHash, + false, /* isRegularExit */ + exitId + ); + withdrawManager.addInput( + exitId, + referenceTxData.age, + exitTxData.signer, + referenceTxData.rootToken + ); + // Even if referenceTx.length == 1, the following input acts as a "dummy" input UTXO to challenge token spends by the exitor + withdrawManager.addInput( + exitId, + _referenceTxData.age, + msg.sender, + referenceTxData.rootToken + ); + } - /** + /** * @notice Verify the deprecation of a state update * @param exit ABI encoded PlasmaExit data * @param inputUtxo ABI encoded Input UTXO data @@ -221,121 +243,135 @@ contract TransferWithSigPredicate is PredicateUtils { * txProof Merkle proof of the challenge tx * @return Whether or not the state is deprecated */ - function verifyDeprecation(bytes calldata exit, bytes calldata inputUtxo, bytes calldata challengeData) - external - view - returns (bool) - { - PlasmaExit memory _exit = decodeExit(exit); - (uint256 age, address utxoOwner, address predicate, address childToken) = decodeInputUtxo(inputUtxo); + function verifyDeprecation( + bytes calldata exit, + bytes calldata inputUtxo, + bytes calldata challengeData + ) external view returns (bool) { + PlasmaExit memory _exit = decodeExit(exit); + (uint256 age, address utxoOwner, address predicate, address childToken) = decodeInputUtxo( + inputUtxo + ); - RLPReader.RLPItem[] memory _challengeData = challengeData.toRlpItem().toList(); - (ExitTxData memory challengeTxData, uint256 expiration) = processExitTx(_challengeData[10].toBytes()); - require( - challengeTxData.signer == utxoOwner, - "utxoOwner is not transferWithSig signer" - ); - // receipt alone is not enough for a challenge. It is required to check that the challenge tx was included as well - // Challenge will be considered successful if a more recent LogTransfer event is found - // Interestingly, that will be determined by erc20/721 predicate - ReferenceTxData memory referenceTxData = processLogTransferReceipt( - predicate, - challengeData, - utxoOwner, - true, /* verifyInclusionInCheckpoint */ - true /* isChallenge */); - // this assertion is required only for erc721 because the spend should correspond to the same NFT - if (registry.predicates(predicate) == Registry.Type.ERC721) { - require( - referenceTxData.closingBalance == _exit.receiptAmountOrNFTId && challengeTxData.amountOrToken == _exit.receiptAmountOrNFTId, - "LogTransferReceipt, challengeTx NFT and challenged utxo NFT do not match" - ); + RLPReader.RLPItem[] memory _challengeData = challengeData + .toRlpItem() + .toList(); + (ExitTxData memory challengeTxData, uint256 expiration) = processExitTx( + _challengeData[10].toBytes() + ); + require( + challengeTxData.signer == utxoOwner, + "utxoOwner is not transferWithSig signer" + ); + // receipt alone is not enough for a challenge. It is required to check that the challenge tx was included as well + // Challenge will be considered successful if a more recent LogTransfer event is found + // Interestingly, that will be determined by erc20/721 predicate + ReferenceTxData memory referenceTxData = processLogTransferReceipt( + predicate, + challengeData, + utxoOwner, + true, /* verifyInclusionInCheckpoint */ + true /* isChallenge */ + ); + // this assertion is required only for erc721 because the spend should correspond to the same NFT + if (registry.predicates(predicate) == Registry.Type.ERC721) { + require( + referenceTxData.closingBalance == _exit.receiptAmountOrNFTId && + challengeTxData.amountOrToken == _exit.receiptAmountOrNFTId, + "LogTransferReceipt, challengeTx NFT and challenged utxo NFT do not match" + ); + } + // assert transferWithSig was still valid when it was included in the child chain + require( + getChildBlockNumberFromAge(referenceTxData.age) <= expiration, + "The transferWithSig order expired when it was included" + ); + require( + referenceTxData.childToken == childToken && + challengeTxData.childToken == childToken, + "LogTransferReceipt, challengeTx token and challenged utxo token do not match" + ); + require( + challengeTxData.txHash != _exit.txHash, + "Cannot challenge with the exit tx" + ); + require( + referenceTxData.age > age, + "Age of challenge log in the receipt needs to be more recent than Utxo being challenged" + ); + return true; } - // assert transferWithSig was still valid when it was included in the child chain - require( - getChildBlockNumberFromAge(referenceTxData.age) <= expiration, - "The transferWithSig order expired when it was included" - ); - require( - referenceTxData.childToken == childToken && challengeTxData.childToken == childToken, - "LogTransferReceipt, challengeTx token and challenged utxo token do not match" - ); - require( - challengeTxData.txHash != _exit.txHash, - "Cannot challenge with the exit tx" - ); - require( - referenceTxData.age > age, - "Age of challenge log in the receipt needs to be more recent than Utxo being challenged" - ); - return true; - } - function getChildBlockNumberFromAge(uint256 age) internal pure returns(uint256) { - // age is represented as (getExitableAt(createdAt) << 127) | (blockNumber << 32) | branchMask.toRlpItem().toUint(); - return (age << 129) >> 161; - } + function getChildBlockNumberFromAge(uint256 age) + internal + pure + returns (uint256) + { + // age is represented as (getExitableAt(createdAt) << 127) | (blockNumber << 32) | branchMask.toRlpItem().toUint(); + return (age << 129) >> 161; + } - function processLogTransferReceipt( - address predicate, - bytes memory preState, - address participant, - bool verifyInclusionInCheckpoint, - bool isChallenge) - internal - view - returns(ReferenceTxData memory _referenceTx) - { - bytes memory _preState = IPredicate(predicate).interpretStateUpdate( - abi.encode( - preState, - participant, - verifyInclusionInCheckpoint, - isChallenge)); - (_referenceTx.closingBalance, - _referenceTx.age, - _referenceTx.childToken, - _referenceTx.rootToken) = abi.decode(_preState, (uint256, uint256, address,address)); - } + function processLogTransferReceipt( + address predicate, + bytes memory preState, + address participant, + bool verifyInclusionInCheckpoint, + bool isChallenge + ) internal view returns (ReferenceTxData memory _referenceTx) { + bytes memory _preState = IPredicate(predicate).interpretStateUpdate( + abi.encode( + preState, + participant, + verifyInclusionInCheckpoint, + isChallenge + ) + ); + ( + _referenceTx.closingBalance, + _referenceTx.age, + _referenceTx.childToken, + _referenceTx.rootToken + ) = abi.decode(_preState, (uint256, uint256, address, address)); + } - /** + /** * @notice Process the challenge transaction * @param exitTx Challenge transaction * @return ExitTxData Parsed challenge transaction data */ - function processExitTx(bytes memory exitTx) - internal - pure - returns(ExitTxData memory txData, uint256 expiration) - { - RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); - require(txList.length == 9, "MALFORMED_TX"); - txData.childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx - address spender; - // Signer of this tx is supposed to be the authorized spender - (spender, txData.txHash) = getAddressFromTx(txList); + function processExitTx(bytes memory exitTx) + internal + pure + returns (ExitTxData memory txData, uint256 expiration) + { + RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); + require(txList.length == 9, "MALFORMED_TX"); + txData.childToken = RLPReader.toAddress(txList[3]); // corresponds to "to" field in tx + address spender; + // Signer of this tx is supposed to be the authorized spender + (spender, txData.txHash) = getAddressFromTx(txList); - bytes memory txPayload = RLPReader.toBytes(txList[5]); - bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txPayload, 0, 4)); - require( - funcSig == TRANSFER_WITH_SIG_FUNC_SIG, - "Not transferWithSig transaction" - ); - // 32 bytes offset - txData.amountOrToken = BytesLib.toUint(txPayload, 36); - bytes32 data = bytes32(BytesLib.toUint(txPayload, 68)); - expiration = BytesLib.toUint(txPayload, 100); - // address to = address(BytesLib.toUint(txPayload, 132)); - uint256 siglength = BytesLib.toUint(txPayload, 164); - bytes memory sig = BytesLib.slice(txPayload, 196, siglength); - bytes32 dataHash = TransferWithSigUtils.getTokenTransferOrderHash( - txData.childToken, - spender, - txData.amountOrToken, - data, - expiration - ); - // The signer of the transfer order is the "real signer" of the exit tx - txData.signer = ECVerify.ecrecovery(dataHash, sig); - } + bytes memory txPayload = RLPReader.toBytes(txList[5]); + bytes4 funcSig = BytesLib.toBytes4(BytesLib.slice(txPayload, 0, 4)); + require( + funcSig == TRANSFER_WITH_SIG_FUNC_SIG, + "Not transferWithSig transaction" + ); + // 32 bytes offset + txData.amountOrToken = BytesLib.toUint(txPayload, 36); + bytes32 data = bytes32(BytesLib.toUint(txPayload, 68)); + expiration = BytesLib.toUint(txPayload, 100); + // address to = address(BytesLib.toUint(txPayload, 132)); + uint256 siglength = BytesLib.toUint(txPayload, 164); + bytes memory sig = BytesLib.slice(txPayload, 196, siglength); + bytes32 dataHash = TransferWithSigUtils.getTokenTransferOrderHash( + txData.childToken, + spender, + txData.amountOrToken, + data, + expiration + ); + // The signer of the transfer order is the "real signer" of the exit tx + txData.signer = ECVerify.ecrecovery(dataHash, sig); + } } diff --git a/contracts/root/stateSyncer/StateSender.sol b/contracts/root/stateSyncer/StateSender.sol index b7cd6f0cb..0525dc404 100644 --- a/contracts/root/stateSyncer/StateSender.sol +++ b/contracts/root/stateSyncer/StateSender.sol @@ -1,40 +1,54 @@ pragma solidity ^0.5.2; -import { Ownable } from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; - +import {Ownable} from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; contract StateSender is Ownable { - using SafeMath for uint256; + using SafeMath for uint256; - uint256 public counter; - mapping(address => address) public registrations; + uint256 public counter; + mapping(address => address) public registrations; - event NewRegistration(address indexed user, address indexed sender, address indexed receiver); - event RegistrationUpdated(address indexed user, address indexed sender, address indexed receiver); - event StateSynced(uint256 indexed id, address indexed contractAddress, bytes data); + event NewRegistration( + address indexed user, + address indexed sender, + address indexed receiver + ); + event RegistrationUpdated( + address indexed user, + address indexed sender, + address indexed receiver + ); + event StateSynced( + uint256 indexed id, + address indexed contractAddress, + bytes data + ); - modifier onlyRegistered(address receiver) { - require(registrations[receiver] == msg.sender, "Invalid sender"); - _; - } + modifier onlyRegistered(address receiver) { + require(registrations[receiver] == msg.sender, "Invalid sender"); + _; + } - function syncState(address receiver, bytes calldata data) external onlyRegistered(receiver) { - counter = counter.add(1); - emit StateSynced(counter, receiver, data); - } + function syncState(address receiver, bytes calldata data) + external + onlyRegistered(receiver) + { + counter = counter.add(1); + emit StateSynced(counter, receiver, data); + } - // register new contract for state sync - function register(address sender, address receiver) public { - require( - isOwner() || registrations[receiver] == msg.sender, - "StateSender.register: Not authorized to register" - ); - registrations[receiver] = sender; - if (registrations[receiver] == address(0)) { - emit NewRegistration(msg.sender, sender, receiver); - } else { - emit RegistrationUpdated(msg.sender, sender, receiver); + // register new contract for state sync + function register(address sender, address receiver) public { + require( + isOwner() || registrations[receiver] == msg.sender, + "StateSender.register: Not authorized to register" + ); + registrations[receiver] = sender; + if (registrations[receiver] == address(0)) { + emit NewRegistration(msg.sender, sender, receiver); + } else { + emit RegistrationUpdated(msg.sender, sender, receiver); + } } - } } diff --git a/contracts/root/withdrawManager/ExitNFT.sol b/contracts/root/withdrawManager/ExitNFT.sol index b22374097..ad50f0603 100644 --- a/contracts/root/withdrawManager/ExitNFT.sol +++ b/contracts/root/withdrawManager/ExitNFT.sol @@ -1,35 +1,35 @@ pragma solidity ^0.5.2; -import { ERC721 } from "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; -import { Registry } from "../../common/Registry.sol"; - +import {ERC721} from "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; +import {Registry} from "../../common/Registry.sol"; contract ExitNFT is ERC721 { - Registry internal registry; - - modifier onlyWithdrawManager() { - require( - msg.sender == registry.getWithdrawManagerAddress(), - "UNAUTHORIZED_WITHDRAW_MANAGER_ONLY" - ); - _; - } - - constructor(address _registry) - public - { - registry = Registry(_registry); - } - - function mint(address _owner, uint256 _tokenId) external onlyWithdrawManager { - _mint(_owner, _tokenId); - } - - function burn(uint256 _tokenId) external onlyWithdrawManager { - _burn(_tokenId); - } - - function exists(uint256 tokenId) public view returns (bool) { - return _exists(tokenId); - } + Registry internal registry; + + modifier onlyWithdrawManager() { + require( + msg.sender == registry.getWithdrawManagerAddress(), + "UNAUTHORIZED_WITHDRAW_MANAGER_ONLY" + ); + _; + } + + constructor(address _registry) public { + registry = Registry(_registry); + } + + function mint(address _owner, uint256 _tokenId) + external + onlyWithdrawManager + { + _mint(_owner, _tokenId); + } + + function burn(uint256 _tokenId) external onlyWithdrawManager { + _burn(_tokenId); + } + + function exists(uint256 tokenId) public view returns (bool) { + return _exists(tokenId); + } } diff --git a/contracts/root/withdrawManager/IWithdrawManager.sol b/contracts/root/withdrawManager/IWithdrawManager.sol index f98c85218..bb872dca1 100644 --- a/contracts/root/withdrawManager/IWithdrawManager.sol +++ b/contracts/root/withdrawManager/IWithdrawManager.sol @@ -1,22 +1,35 @@ pragma solidity ^0.5.2; - contract IWithdrawManager { - function createExitQueue(address token) external; + function createExitQueue(address token) external; - function verifyInclusion(bytes calldata data, uint8 offset, bool verifyTxInclusion) external view returns (uint256 age); + function verifyInclusion( + bytes calldata data, + uint8 offset, + bool verifyTxInclusion + ) external view returns (uint256 age); - function addExitToQueue( - address exitor, - address childToken, - address rootToken, - uint256 exitAmountOrTokenId, - bytes32 txHash, - bool isRegularExit, - uint256 priority) - external; + function addExitToQueue( + address exitor, + address childToken, + address rootToken, + uint256 exitAmountOrTokenId, + bytes32 txHash, + bool isRegularExit, + uint256 priority + ) external; - function addInput(uint256 exitId, uint256 age, address utxoOwner, address token) external; + function addInput( + uint256 exitId, + uint256 age, + address utxoOwner, + address token + ) external; - function challengeExit(uint256 exitId, uint256 inputId, bytes calldata challengeData, address adjudicatorPredicate) external; + function challengeExit( + uint256 exitId, + uint256 inputId, + bytes calldata challengeData, + address adjudicatorPredicate + ) external; } diff --git a/contracts/root/withdrawManager/WithdrawManager.sol b/contracts/root/withdrawManager/WithdrawManager.sol index 4f8ea4a46..6496b21b2 100644 --- a/contracts/root/withdrawManager/WithdrawManager.sol +++ b/contracts/root/withdrawManager/WithdrawManager.sol @@ -1,77 +1,73 @@ pragma solidity ^0.5.2; -import { ERC20 } from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; -import { ERC721 } from "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; -import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; +import {ERC20} from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; +import {ERC721} from "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; +import {Math} from "openzeppelin-solidity/contracts/math/Math.sol"; +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; + +import {Merkle} from "../../common/lib/Merkle.sol"; +import {MerklePatriciaProof} from "../../common/lib/MerklePatriciaProof.sol"; +import {PriorityQueue} from "../../common/lib/PriorityQueue.sol"; + +import {ExitNFT} from "./ExitNFT.sol"; +import {DepositManager} from "../depositManager/DepositManager.sol"; +import {IPredicate} from "../predicates/IPredicate.sol"; +import {IWithdrawManager} from "./IWithdrawManager.sol"; +import {RootChainHeader} from "../RootChainStorage.sol"; +import {Registry} from "../../common/Registry.sol"; +import {WithdrawManagerStorage} from "./WithdrawManagerStorage.sol"; -import { Merkle } from "../../common/lib/Merkle.sol"; -import { MerklePatriciaProof } from "../../common/lib/MerklePatriciaProof.sol"; -import { PriorityQueue } from "../../common/lib/PriorityQueue.sol"; +contract WithdrawManager is WithdrawManagerStorage, IWithdrawManager { + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + using Merkle for bytes32; -import { ExitNFT } from "./ExitNFT.sol"; -import { DepositManager } from "../depositManager/DepositManager.sol"; -import { IPredicate } from "../predicates/IPredicate.sol"; -import { IWithdrawManager } from "./IWithdrawManager.sol"; -import { RootChainHeader } from "../RootChainStorage.sol"; -import { Registry } from "../../common/Registry.sol"; -import { WithdrawManagerStorage } from "./WithdrawManagerStorage.sol"; + modifier isBondProvided() { + require(msg.value == BOND_AMOUNT, "Invalid Bond amount"); + _; + } + modifier isPredicateAuthorized() { + require( + registry.predicates(msg.sender) != Registry.Type.Invalid, + "PREDICATE_NOT_AUTHORIZED" + ); + _; + } -contract WithdrawManager is WithdrawManagerStorage, IWithdrawManager { - using RLPReader for bytes; - using RLPReader for RLPReader.RLPItem; - using Merkle for bytes32; - - modifier isBondProvided() { - require(msg.value == BOND_AMOUNT, "Invalid Bond amount"); - _; - } - - modifier isPredicateAuthorized() { - require( - registry.predicates(msg.sender) != Registry.Type.Invalid, - "PREDICATE_NOT_AUTHORIZED" - ); - _; - } - - modifier checkPredicateAndTokenMapping(address rootToken) { - Registry.Type _type = registry.predicates(msg.sender); - require( - registry.rootToChildToken(rootToken) != address(0x0), - "rootToken not supported" - ); - if (_type == Registry.Type.ERC20) { - require( - registry.isERC721(rootToken) == false, - "Predicate supports only ERC20 tokens" - ); - } else if (_type == Registry.Type.ERC721) { - require( - registry.isERC721(rootToken) == true, - "Predicate supports only ERC721 tokens" - ); - } else if (_type == Registry.Type.Custom) { - } else { - revert("PREDICATE_NOT_AUTHORIZED"); + modifier checkPredicateAndTokenMapping(address rootToken) { + Registry.Type _type = registry.predicates(msg.sender); + require( + registry.rootToChildToken(rootToken) != address(0x0), + "rootToken not supported" + ); + if (_type == Registry.Type.ERC20) { + require( + registry.isERC721(rootToken) == false, + "Predicate supports only ERC20 tokens" + ); + } else if (_type == Registry.Type.ERC721) { + require( + registry.isERC721(rootToken) == true, + "Predicate supports only ERC721 tokens" + ); + } else if (_type == Registry.Type.Custom) {} else { + revert("PREDICATE_NOT_AUTHORIZED"); + } + _; } - _; - } /** * @dev Receive bond for bonded exits */ - function () external payable {} + function() external payable {} - function createExitQueue(address token) - external - { - require(msg.sender == address(registry), "UNAUTHORIZED_REGISTRY_ONLY"); - exitsQueues[token] = address(new PriorityQueue()); - } + function createExitQueue(address token) external { + require(msg.sender == address(registry), "UNAUTHORIZED_REGISTRY_ONLY"); + exitsQueues[token] = address(new PriorityQueue()); + } - /** + /** * @dev Verify the inclusion of the receipt in the checkpoint * @param data RLP encoded data of the reference tx(s) that encodes the following fields for each tx * headerNumber Header block number of which the reference tx was a part of @@ -88,172 +84,205 @@ contract WithdrawManager is WithdrawManagerStorage, IWithdrawManager { * @param verifyTxInclusion Whether to also verify the inclusion of the raw tx in the txRoot * @return ageOfInput Measure of the position of the receipt and the log in the child chain */ - function verifyInclusion(bytes calldata data, uint8 offset, bool verifyTxInclusion) - external - view - returns (uint256 /* ageOfInput */) - { - RLPReader.RLPItem[] memory referenceTxData = data.toRlpItem().toList(); - uint256 headerNumber = referenceTxData[offset].toUint(); - bytes memory branchMask = referenceTxData[offset + 8].toBytes(); - require( - MerklePatriciaProof.verify( - referenceTxData[offset + 6].toBytes(), // receipt - branchMask, - referenceTxData[offset + 7].toBytes(), // receiptProof - bytes32(referenceTxData[offset + 5].toUint()) // receiptsRoot - ), - "INVALID_RECEIPT_MERKLE_PROOF" - ); - - if (verifyTxInclusion) { - require( - MerklePatriciaProof.verify( - referenceTxData[offset + 10].toBytes(), // tx - branchMask, - referenceTxData[offset + 11].toBytes(), // txProof - bytes32(referenceTxData[offset + 4].toUint()) // txRoot - ), - "INVALID_TX_MERKLE_PROOF" - ); + function verifyInclusion( + bytes calldata data, + uint8 offset, + bool verifyTxInclusion + ) + external + view + returns ( + uint256 /* ageOfInput */ + ) + { + RLPReader.RLPItem[] memory referenceTxData = data.toRlpItem().toList(); + uint256 headerNumber = referenceTxData[offset].toUint(); + bytes memory branchMask = referenceTxData[offset + 8].toBytes(); + require( + MerklePatriciaProof.verify( + referenceTxData[offset + 6].toBytes(), // receipt + branchMask, + referenceTxData[offset + 7].toBytes(), // receiptProof + bytes32(referenceTxData[offset + 5].toUint()) // receiptsRoot + ), + "INVALID_RECEIPT_MERKLE_PROOF" + ); + + if (verifyTxInclusion) { + require( + MerklePatriciaProof.verify( + referenceTxData[offset + 10].toBytes(), // tx + branchMask, + referenceTxData[offset + 11].toBytes(), // txProof + bytes32(referenceTxData[offset + 4].toUint()) // txRoot + ), + "INVALID_TX_MERKLE_PROOF" + ); + } + + uint256 blockNumber = referenceTxData[offset + 2].toUint(); + uint256 createdAt = checkBlockMembershipInCheckpoint( + blockNumber, + referenceTxData[offset + 3].toUint(), // blockTime + bytes32(referenceTxData[offset + 4].toUint()), // txRoot + bytes32(referenceTxData[offset + 5].toUint()), // receiptRoot + headerNumber, + referenceTxData[offset + 1].toBytes() // blockProof + ); + + uint256 _branchMask = branchMask.toRlpItem().toUint(); + require( + _branchMask & + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000 == + 0, + "Branch mask should be 32 bits" + ); + // ageOfInput is denoted as + // 1 reserve bit (see last 2 lines in comment) + // 128 bits for exitableAt timestamp + // 95 bits for child block number + // 32 bits for receiptPos + logIndex * MAX_LOGS + oIndex + // In predicates, the exitId will be evaluated by shifting the ageOfInput left by 1 bit + // (Only in erc20Predicate) Last bit is to differentiate whether the sender or receiver of the in-flight tx is starting an exit + return + (getExitableAt(createdAt) << 127) | + (blockNumber << 32) | + _branchMask; + } + + function startExitWithDepositedTokens( + uint256 depositId, + address token, + uint256 amountOrToken + ) external payable isBondProvided { + (bytes32 depositHash, uint256 createdAt) = getDepositManager().deposits( + depositId + ); + require( + keccak256(abi.encodePacked(msg.sender, token, amountOrToken)) == + depositHash, + "UNAUTHORIZED_EXIT" + ); + uint256 ageOfInput = getExitableAt(createdAt) << 127; + uint256 exitId = ageOfInput << 1; + address predicate = registry.isTokenMappedAndGetPredicate(token); + _addExitToQueue( + msg.sender, + token, + amountOrToken, + bytes32(0), /* txHash */ + false, /* isRegularExit */ + exitId + ); + _addInput( + exitId, + ageOfInput, + msg.sender, + /* utxoOwner */ + predicate, + token + ); + } + + function addExitToQueue( + address exitor, + address childToken, + address rootToken, + uint256 exitAmountOrTokenId, + bytes32 txHash, + bool isRegularExit, + uint256 priority + ) external checkPredicateAndTokenMapping(rootToken) { + require( + registry.rootToChildToken(rootToken) == childToken, + "INVALID_ROOT_TO_CHILD_TOKEN_MAPPING" + ); + _addExitToQueue( + exitor, + rootToken, + exitAmountOrTokenId, + txHash, + isRegularExit, + priority + ); } - uint256 blockNumber = referenceTxData[offset + 2].toUint(); - uint256 createdAt = checkBlockMembershipInCheckpoint( - blockNumber, - referenceTxData[offset + 3].toUint(), // blockTime - bytes32(referenceTxData[offset + 4].toUint()), // txRoot - bytes32(referenceTxData[offset + 5].toUint()), // receiptRoot - headerNumber, - referenceTxData[offset + 1].toBytes() // blockProof - ); - - uint256 _branchMask = branchMask.toRlpItem().toUint(); - require( - _branchMask & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000 == 0, - "Branch mask should be 32 bits" - ); - // ageOfInput is denoted as - // 1 reserve bit (see last 2 lines in comment) - // 128 bits for exitableAt timestamp - // 95 bits for child block number - // 32 bits for receiptPos + logIndex * MAX_LOGS + oIndex - // In predicates, the exitId will be evaluated by shifting the ageOfInput left by 1 bit - // (Only in erc20Predicate) Last bit is to differentiate whether the sender or receiver of the in-flight tx is starting an exit - return (getExitableAt(createdAt) << 127) | (blockNumber << 32) | _branchMask; - } - - function startExitWithDepositedTokens(uint256 depositId, address token, uint256 amountOrToken) - external - payable - isBondProvided - { - (bytes32 depositHash, uint256 createdAt) = getDepositManager().deposits(depositId); - require( - keccak256(abi.encodePacked(msg.sender, token, amountOrToken)) == depositHash, - "UNAUTHORIZED_EXIT" - ); - uint256 ageOfInput = getExitableAt(createdAt) << 127; - uint256 exitId = ageOfInput << 1; - address predicate = registry.isTokenMappedAndGetPredicate(token); - _addExitToQueue( - msg.sender, - token, - amountOrToken, - bytes32(0), /* txHash */ - false,/* isRegularExit */ - exitId); - _addInput(exitId, ageOfInput, msg.sender, /* utxoOwner */ predicate, token); - } - - function addExitToQueue( - address exitor, - address childToken, - address rootToken, - uint256 exitAmountOrTokenId, - bytes32 txHash, - bool isRegularExit, - uint256 priority) - external - checkPredicateAndTokenMapping(rootToken) - { - require( - registry.rootToChildToken(rootToken) == childToken, - "INVALID_ROOT_TO_CHILD_TOKEN_MAPPING" - ); - _addExitToQueue(exitor, rootToken, exitAmountOrTokenId, txHash, isRegularExit, priority); - } - - function challengeExit( - uint256 exitId, - uint256 inputId, - bytes calldata challengeData, - address adjudicatorPredicate) - external - { - PlasmaExit storage exit = exits[exitId]; - Input storage input = exit.inputs[inputId]; - require( - exit.owner != address(0x0) && input.utxoOwner != address(0x0), - "Invalid exit or input id" - ); - require( - registry.predicates(adjudicatorPredicate) != Registry.Type.Invalid, - "INVALID_PREDICATE" - ); - require( - IPredicate(adjudicatorPredicate).verifyDeprecation( - encodeExit(exit), - encodeInputUtxo(inputId, input), - challengeData - ), - "Challenge failed" - ); - // In the call to burn(exitId), there is an implicit check that prevents challenging the same exit twice - ExitNFT(exitNft).burn(exitId); - - // Send bond amount to challenger - msg.sender.transfer(BOND_AMOUNT); - - // delete exits[exitId]; - emit ExitCancelled(exitId); - } - - function processExits(address _token) - external - { - uint256 exitableAt; - uint256 exitId; - - PriorityQueue exitQueue = PriorityQueue(exitsQueues[_token]); - - while (exitQueue.currentSize() > 0 && gasleft() > ON_FINALIZE_GAS_LIMIT) { - (exitableAt, exitId) = exitQueue.getMin(); - exitId = exitableAt << 128 | exitId; - PlasmaExit memory currentExit = exits[exitId]; - - // Stop processing exits if the exit that is next is queue is still in its challenge period - if (exitableAt > block.timestamp) return; - - exitQueue.delMin(); - // If the exitNft was deleted as a result of a challenge, skip processing this exit - if (!exitNft.exists(exitId)) continue; - - exitNft.burn(exitId); - - // limit the gas amount that predicate.onFinalizeExit() can use, to be able to make gas estimations for bulk process exits - address exitor = currentExit.owner; - IPredicate(currentExit.predicate).onFinalizeExit(encodeExitForProcessExit(exitId)); - emit Withdraw(exitId, exitor, _token, currentExit.receiptAmountOrNFTId); - - if (!currentExit.isRegularExit) { - // return the bond amount if this was a MoreVp style exit - address(uint160(exitor)).transfer(BOND_AMOUNT); - } + function challengeExit( + uint256 exitId, + uint256 inputId, + bytes calldata challengeData, + address adjudicatorPredicate + ) external { + PlasmaExit storage exit = exits[exitId]; + Input storage input = exit.inputs[inputId]; + require( + exit.owner != address(0x0) && input.utxoOwner != address(0x0), + "Invalid exit or input id" + ); + require( + registry.predicates(adjudicatorPredicate) != Registry.Type.Invalid, + "INVALID_PREDICATE" + ); + require( + IPredicate(adjudicatorPredicate).verifyDeprecation( + encodeExit(exit), + encodeInputUtxo(inputId, input), + challengeData + ), + "Challenge failed" + ); + // In the call to burn(exitId), there is an implicit check that prevents challenging the same exit twice + ExitNFT(exitNft).burn(exitId); + + // Send bond amount to challenger + msg.sender.transfer(BOND_AMOUNT); + + // delete exits[exitId]; + emit ExitCancelled(exitId); } - } - /** + function processExits(address _token) external { + uint256 exitableAt; + uint256 exitId; + + PriorityQueue exitQueue = PriorityQueue(exitsQueues[_token]); + + while ( + exitQueue.currentSize() > 0 && gasleft() > ON_FINALIZE_GAS_LIMIT + ) { + (exitableAt, exitId) = exitQueue.getMin(); + exitId = (exitableAt << 128) | exitId; + PlasmaExit memory currentExit = exits[exitId]; + + // Stop processing exits if the exit that is next is queue is still in its challenge period + if (exitableAt > block.timestamp) return; + + exitQueue.delMin(); + // If the exitNft was deleted as a result of a challenge, skip processing this exit + if (!exitNft.exists(exitId)) continue; + + exitNft.burn(exitId); + + // limit the gas amount that predicate.onFinalizeExit() can use, to be able to make gas estimations for bulk process exits + address exitor = currentExit.owner; + IPredicate(currentExit.predicate).onFinalizeExit( + encodeExitForProcessExit(exitId) + ); + emit Withdraw( + exitId, + exitor, + _token, + currentExit.receiptAmountOrNFTId + ); + + if (!currentExit.isRegularExit) { + // return the bond amount if this was a MoreVp style exit + address(uint160(exitor)).transfer(BOND_AMOUNT); + } + } + } + + /** * @dev Add a state update (UTXO style input) to an exit * @param exitId Exit ID * @param age age of the UTXO style input @@ -261,125 +290,184 @@ contract WithdrawManager is WithdrawManagerStorage, IWithdrawManager { * (alternate expression) User who could have potentially spent this UTXO * @param token Token (Think of it like Utxo color) */ - function addInput(uint256 exitId, uint256 age, address utxoOwner, address token) - external - isPredicateAuthorized - { - PlasmaExit storage exitObject = exits[exitId]; - require( - exitObject.owner != address(0x0), - "INVALID_EXIT_ID" - ); - _addInput(exitId, age, utxoOwner, /* predicate */ msg.sender, token); - } - - function _addInput(uint256 exitId, uint256 age, address utxoOwner, address predicate, address token) - internal - { - exits[exitId].inputs[age] = Input(utxoOwner, predicate, token); - emit ExitUpdated(exitId, age, utxoOwner); - } - - function encodeExit(PlasmaExit storage exit) - internal - view - returns (bytes memory) - { - return abi.encode(exit.owner, registry.rootToChildToken(exit.token), exit.receiptAmountOrNFTId, exit.txHash, exit.isRegularExit); - } - - function encodeExitForProcessExit(uint256 exitId) - internal - view - returns (bytes memory) - { - PlasmaExit storage exit = exits[exitId]; - return abi.encode(exitId, exit.token, exit.owner, exit.receiptAmountOrNFTId); - } - - function encodeInputUtxo(uint256 age, Input storage input) - internal - view - returns (bytes memory) - { - return abi.encode(age, input.utxoOwner, input.predicate, registry.rootToChildToken(input.token)); - } - - function _addExitToQueue( - address exitor, - address rootToken, - uint256 exitAmountOrTokenId, - bytes32 txHash, - bool isRegularExit, - uint256 exitId) - internal - { - require( - exits[exitId].token == address(0x0), - "EXIT_ALREADY_EXISTS" - ); - exits[exitId] = PlasmaExit(exitAmountOrTokenId, txHash, exitor, rootToken, isRegularExit, /* predicate */msg.sender); - PlasmaExit storage _exitObject = exits[exitId]; - - bytes32 key = getKey(_exitObject.token, _exitObject.owner, _exitObject.receiptAmountOrNFTId); - - if (!isRegularExit) { - // a user cannot start 2 MoreVP exits for the same erc20 token or nft - require(ownerExits[key] == 0, "EXIT_ALREADY_IN_PROGRESS"); - ownerExits[key] = exitId; + function addInput( + uint256 exitId, + uint256 age, + address utxoOwner, + address token + ) external isPredicateAuthorized { + PlasmaExit storage exitObject = exits[exitId]; + require(exitObject.owner != address(0x0), "INVALID_EXIT_ID"); + _addInput( + exitId, + age, + utxoOwner, + /* predicate */ + msg.sender, + token + ); } - PriorityQueue queue = PriorityQueue(exitsQueues[_exitObject.token]); - - // Way priority queue is implemented is such that it expects 2 uint256 params with most significant 128 bits masked out - // This is a workaround to split exitId, which otherwise is conclusive in itself - // exitId >> 128 gives 128 most significant bits - // uint256(uint128(exitId)) gives 128 least significant bits - // @todo Fix this mess - queue.insert(exitId >> 128, uint256(uint128(exitId))); - - // create exit nft - exitNft.mint(_exitObject.owner, exitId); - emit ExitStarted(exitor, exitId, rootToken, exitAmountOrTokenId, isRegularExit); - } - - function checkBlockMembershipInCheckpoint( - uint256 blockNumber, - uint256 blockTime, - bytes32 txRoot, - bytes32 receiptRoot, - uint256 headerNumber, - bytes memory blockProof) - internal view returns(uint256 /* createdAt */) - { - (bytes32 headerRoot, uint256 startBlock,,uint256 createdAt,) = rootChain.headerBlocks(headerNumber); - require( - keccak256(abi.encodePacked(blockNumber, blockTime, txRoot, receiptRoot)) - .checkMembership(blockNumber - startBlock, headerRoot, blockProof), - "WITHDRAW_BLOCK_NOT_A_PART_OF_SUBMITTED_HEADER" - ); - return createdAt; - } - - function getKey(address token, address exitor, uint256 amountOrToken) - internal - view - returns (bytes32 key) - { - if (registry.isERC721(token)) { - key = keccak256(abi.encodePacked(token, exitor, amountOrToken)); - } else { - // validate amount - require(amountOrToken > 0, "CANNOT_EXIT_ZERO_AMOUNTS"); - key = keccak256(abi.encodePacked(token, exitor)); + function _addInput( + uint256 exitId, + uint256 age, + address utxoOwner, + address predicate, + address token + ) internal { + exits[exitId].inputs[age] = Input(utxoOwner, predicate, token); + emit ExitUpdated(exitId, age, utxoOwner); } - } - function getDepositManager() internal view returns (DepositManager) { - return DepositManager(address(uint160(registry.getDepositManagerAddress()))); - } + function encodeExit(PlasmaExit storage exit) + internal + view + returns (bytes memory) + { + return + abi.encode( + exit.owner, + registry.rootToChildToken(exit.token), + exit.receiptAmountOrNFTId, + exit.txHash, + exit.isRegularExit + ); + } - function getExitableAt(uint256 createdAt) internal view returns (uint256) { - return Math.max(createdAt + 2 * HALF_EXIT_PERIOD, now + HALF_EXIT_PERIOD); - } + function encodeExitForProcessExit(uint256 exitId) + internal + view + returns (bytes memory) + { + PlasmaExit storage exit = exits[exitId]; + return + abi.encode( + exitId, + exit.token, + exit.owner, + exit.receiptAmountOrNFTId + ); + } + + function encodeInputUtxo(uint256 age, Input storage input) + internal + view + returns (bytes memory) + { + return + abi.encode( + age, + input.utxoOwner, + input.predicate, + registry.rootToChildToken(input.token) + ); + } + + function _addExitToQueue( + address exitor, + address rootToken, + uint256 exitAmountOrTokenId, + bytes32 txHash, + bool isRegularExit, + uint256 exitId + ) internal { + require(exits[exitId].token == address(0x0), "EXIT_ALREADY_EXISTS"); + exits[exitId] = PlasmaExit( + exitAmountOrTokenId, + txHash, + exitor, + rootToken, + isRegularExit, + /* predicate */ + msg.sender + ); + PlasmaExit storage _exitObject = exits[exitId]; + + bytes32 key = getKey( + _exitObject.token, + _exitObject.owner, + _exitObject.receiptAmountOrNFTId + ); + + if (!isRegularExit) { + // a user cannot start 2 MoreVP exits for the same erc20 token or nft + require(ownerExits[key] == 0, "EXIT_ALREADY_IN_PROGRESS"); + ownerExits[key] = exitId; + } + + PriorityQueue queue = PriorityQueue(exitsQueues[_exitObject.token]); + + // Way priority queue is implemented is such that it expects 2 uint256 params with most significant 128 bits masked out + // This is a workaround to split exitId, which otherwise is conclusive in itself + // exitId >> 128 gives 128 most significant bits + // uint256(uint128(exitId)) gives 128 least significant bits + // @todo Fix this mess + queue.insert(exitId >> 128, uint256(uint128(exitId))); + + // create exit nft + exitNft.mint(_exitObject.owner, exitId); + emit ExitStarted( + exitor, + exitId, + rootToken, + exitAmountOrTokenId, + isRegularExit + ); + } + + function checkBlockMembershipInCheckpoint( + uint256 blockNumber, + uint256 blockTime, + bytes32 txRoot, + bytes32 receiptRoot, + uint256 headerNumber, + bytes memory blockProof + ) + internal + view + returns ( + uint256 /* createdAt */ + ) + { + (bytes32 headerRoot, uint256 startBlock, , uint256 createdAt, ) = rootChain + .headerBlocks(headerNumber); + require( + keccak256( + abi.encodePacked(blockNumber, blockTime, txRoot, receiptRoot) + ) + .checkMembership( + blockNumber - startBlock, + headerRoot, + blockProof + ), + "WITHDRAW_BLOCK_NOT_A_PART_OF_SUBMITTED_HEADER" + ); + return createdAt; + } + + function getKey(address token, address exitor, uint256 amountOrToken) + internal + view + returns (bytes32 key) + { + if (registry.isERC721(token)) { + key = keccak256(abi.encodePacked(token, exitor, amountOrToken)); + } else { + // validate amount + require(amountOrToken > 0, "CANNOT_EXIT_ZERO_AMOUNTS"); + key = keccak256(abi.encodePacked(token, exitor)); + } + } + + function getDepositManager() internal view returns (DepositManager) { + return + DepositManager( + address(uint160(registry.getDepositManagerAddress())) + ); + } + + function getExitableAt(uint256 createdAt) internal view returns (uint256) { + return + Math.max(createdAt + 2 * HALF_EXIT_PERIOD, now + HALF_EXIT_PERIOD); + } } diff --git a/contracts/root/withdrawManager/WithdrawManagerProxy.sol b/contracts/root/withdrawManager/WithdrawManagerProxy.sol index 110b2643b..751269070 100644 --- a/contracts/root/withdrawManager/WithdrawManagerProxy.sol +++ b/contracts/root/withdrawManager/WithdrawManagerProxy.sol @@ -1,19 +1,20 @@ pragma solidity ^0.5.2; -import { Registry } from "../../common/Registry.sol"; -import { Proxy } from "../../common/misc/Proxy.sol"; -import { WithdrawManagerStorage } from "./WithdrawManagerStorage.sol"; -import { RootChain } from "../RootChain.sol"; -import { ExitNFT } from "./ExitNFT.sol"; - +import {Registry} from "../../common/Registry.sol"; +import {Proxy} from "../../common/misc/Proxy.sol"; +import {WithdrawManagerStorage} from "./WithdrawManagerStorage.sol"; +import {RootChain} from "../RootChain.sol"; +import {ExitNFT} from "./ExitNFT.sol"; contract WithdrawManagerProxy is Proxy, WithdrawManagerStorage { - constructor(address _proxyTo, address _registry, address _rootChain, address _exitNft) - public - Proxy(_proxyTo) - { - registry = Registry(_registry); - rootChain = RootChain(_rootChain); - exitNft = ExitNFT(_exitNft); - } + constructor( + address _proxyTo, + address _registry, + address _rootChain, + address _exitNft + ) public Proxy(_proxyTo) { + registry = Registry(_registry); + rootChain = RootChain(_rootChain); + exitNft = ExitNFT(_exitNft); + } } diff --git a/contracts/root/withdrawManager/WithdrawManagerStorage.sol b/contracts/root/withdrawManager/WithdrawManagerStorage.sol index 2eb7e1f20..ebc80b52e 100644 --- a/contracts/root/withdrawManager/WithdrawManagerStorage.sol +++ b/contracts/root/withdrawManager/WithdrawManagerStorage.sol @@ -1,78 +1,75 @@ pragma solidity ^0.5.2; -import { ProxyStorage } from "../../common/misc/ProxyStorage.sol"; -import { Registry } from "../../common/Registry.sol"; -import { RootChain } from "../RootChain.sol"; -import { ExitNFT } from "./ExitNFT.sol"; - +import {ProxyStorage} from "../../common/misc/ProxyStorage.sol"; +import {Registry} from "../../common/Registry.sol"; +import {RootChain} from "../RootChain.sol"; +import {ExitNFT} from "./ExitNFT.sol"; contract ExitsDataStructure { - struct Input { - address utxoOwner; - address predicate; - address token; - } - - struct PlasmaExit { - uint256 receiptAmountOrNFTId; - bytes32 txHash; - address owner; - address token; - bool isRegularExit; - address predicate; - // Mapping from age of input to Input - mapping(uint256 => Input) inputs; - } + struct Input { + address utxoOwner; + address predicate; + address token; + } + + struct PlasmaExit { + uint256 receiptAmountOrNFTId; + bytes32 txHash; + address owner; + address token; + bool isRegularExit; + address predicate; + // Mapping from age of input to Input + mapping(uint256 => Input) inputs; + } } - contract WithdrawManagerHeader is ExitsDataStructure { - event Withdraw( - uint256 indexed exitId, - address indexed user, - address indexed token, - uint256 amount - ); - - event ExitStarted( - address indexed exitor, - uint256 indexed exitId, - address indexed token, - uint256 amount, - bool isRegularExit - ); - - event ExitUpdated( - uint256 indexed exitId, - uint256 indexed age, - address signer - ); - - event ExitCancelled(uint256 indexed exitId); + event Withdraw( + uint256 indexed exitId, + address indexed user, + address indexed token, + uint256 amount + ); + + event ExitStarted( + address indexed exitor, + uint256 indexed exitId, + address indexed token, + uint256 amount, + bool isRegularExit + ); + + event ExitUpdated( + uint256 indexed exitId, + uint256 indexed age, + address signer + ); + + event ExitCancelled(uint256 indexed exitId); } - contract WithdrawManagerStorage is ProxyStorage, WithdrawManagerHeader { - uint256 internal constant HALF_EXIT_PERIOD = 1 weeks; + uint256 internal constant HALF_EXIT_PERIOD = 1 weeks; - // Bonded exits collaterized at 0.1 ETH - uint256 constant internal BOND_AMOUNT = 10 ** 17; + // Bonded exits collaterized at 0.1 ETH + uint256 internal constant BOND_AMOUNT = 10**17; - Registry internal registry; - RootChain internal rootChain; + Registry internal registry; + RootChain internal rootChain; - mapping (uint256 => PlasmaExit) public exits; - // mapping with token => (owner => exitId) keccak(token+owner) keccak(token+owner+tokenId) - mapping (bytes32 => uint256) public ownerExits; - mapping (address => address) public exitsQueues; - ExitNFT public exitNft; + mapping(uint256 => PlasmaExit) public exits; + // mapping with token => (owner => exitId) keccak(token+owner) keccak(token+owner+tokenId) + mapping(bytes32 => uint256) public ownerExits; + mapping(address => address) public exitsQueues; + ExitNFT public exitNft; - // ERC721, ERC20 and Weth transfers require 155000, 100000, 52000 gas respectively - // Processing each exit in a while loop iteration requires ~52000 gas (@todo check if this changed) - // uint32 constant internal ITERATION_GAS = 52000; + // ERC721, ERC20 and Weth transfers require 155000, 100000, 52000 gas respectively + // Processing each exit in a while loop iteration requires ~52000 gas (@todo check if this changed) + // uint32 constant internal ITERATION_GAS = 52000; - // So putting an upper limit of 155000 + 52000 + leeway - uint32 constant internal ON_FINALIZE_GAS_LIMIT = 210000; + // So putting an upper limit of 155000 + 52000 + leeway + uint32 internal constant ON_FINALIZE_GAS_LIMIT = 210000; - uint256 public exitWindow; + uint256 public exitWindow; } diff --git a/contracts/staking/IStakeManager.sol b/contracts/staking/IStakeManager.sol index e84298463..fe03185c2 100644 --- a/contracts/staking/IStakeManager.sol +++ b/contracts/staking/IStakeManager.sol @@ -1,40 +1,66 @@ pragma solidity ^0.5.2; -import { IERC721Full } from "openzeppelin-solidity/contracts/token/ERC721/IERC721Full.sol"; +import "openzeppelin-solidity/contracts/token/ERC721/IERC721Full.sol"; contract IStakeManager is IERC721Full { - // Todo: fix WITHDRAWAL_DELAY with interface - uint256 public WITHDRAWAL_DELAY = (2**13)/2; // unit: epoch - uint256 public currentEpoch = 1; + // Todo: fix WITHDRAWAL_DELAY with interface + uint256 public WITHDRAWAL_DELAY = (2**13) / 2; // unit: epoch + uint256 public currentEpoch = 1; - enum Status { Inactive, Active, Locked, Unstaked} - struct Validator { - uint256 amount; - uint256 reward; - uint256 activationEpoch; - uint256 deactivationEpoch; - uint256 jailTime; - address signer; - address contractAddress; - Status status; - } + enum Status {Inactive, Active, Locked, Unstaked} + struct Validator { + uint256 amount; + uint256 reward; + uint256 activationEpoch; + uint256 deactivationEpoch; + uint256 jailTime; + address signer; + address contractAddress; + Status status; + } - mapping (uint256 => Validator) public validators; - // validator replacement - function startAuction(uint256 validatorId, uint256 amount) external; - function confirmAuctionBid(uint256 validatorId, uint256 heimdallFee, address signer, bool isContract) external; + mapping(uint256 => Validator) public validators; + // validator replacement + function startAuction(uint256 validatorId, uint256 amount) external; + function confirmAuctionBid( + uint256 validatorId, + uint256 heimdallFee, + address signer, + bool isContract + ) external; - function delegationTransfer(uint256 validatorId, uint256 amount, address delegator) external; - function stake(uint256 amount, uint256 heimdallFee, address signer, bool isContract) external; - function unstake(uint256 validatorId) external; - function totalStakedFor(address addr) external view returns (uint256); - function supportsHistory() external pure returns (bool); - function stakeFor(address user, uint256 heimdallFee, uint256 amount, address signer, bool isContract) public; - function checkSignatures(uint256 blockInterval, bytes32 voteHash, bytes32 stateRoot, bytes memory sigs) public returns(uint256); - function updateValidatorState(uint256 validatorId, int256 amount) public; + function delegationTransfer( + uint256 validatorId, + uint256 amount, + address delegator + ) external; + function stake( + uint256 amount, + uint256 heimdallFee, + address signer, + bool isContract + ) external; + function unstake(uint256 validatorId) external; + function totalStakedFor(address addr) external view returns (uint256); + function supportsHistory() external pure returns (bool); + function stakeFor( + address user, + uint256 heimdallFee, + uint256 amount, + address signer, + bool isContract + ) public; + function checkSignatures( + uint256 blockInterval, + bytes32 voteHash, + bytes32 stateRoot, + bytes memory sigs + ) public returns (uint256); + function updateValidatorState(uint256 validatorId, int256 amount) public; + + // optional + // function lastStakedFor(address addr) external view returns (uint256); + // function totalStakedForAt(address addr, uint256 blockNumber) external view returns (uint256); + // function totalStakedAt(uint256 blockNumber) external view returns (uint256); - // optional - // function lastStakedFor(address addr) external view returns (uint256); - // function totalStakedForAt(address addr, uint256 blockNumber) external view returns (uint256); - // function totalStakedAt(uint256 blockNumber) external view returns (uint256); } diff --git a/contracts/staking/IValidatorShare.sol b/contracts/staking/IValidatorShare.sol index 02404e1f6..f5b17a131 100644 --- a/contracts/staking/IValidatorShare.sol +++ b/contracts/staking/IValidatorShare.sol @@ -1,54 +1,60 @@ pragma solidity ^0.5.2; -import { ERC20 } from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import { ERC721Full } from "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol"; - -import { Lockable } from "../common/mixin/Lockable.sol"; -import { StakingInfo } from "./StakingInfo.sol"; -import { IStakeManager } from "./IStakeManager.sol"; +import {ERC20} from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol"; +import {Lockable} from "../common/mixin/Lockable.sol"; +import {StakingInfo} from "./StakingInfo.sol"; +import {IStakeManager} from "./IStakeManager.sol"; contract IValidatorShare is ERC20, Lockable { - using SafeMath for uint256; - ERC20 public token; - StakingInfo public stakingLogger; - IStakeManager public stakeManager; - uint256 public validatorId; - uint256 public validatorRewards; - uint256 public commissionRate; - uint256 public validatorDelegatorRatio = 10; - - uint256 public totalStake; - uint256 public rewards; - uint256 public activeAmount; - bool public delegation = true; - - struct Delegator { - uint256 amount; - uint256 withdrawEpoch; - } - - mapping (address => uint256) public amountStaked; - mapping (address=> Delegator) public delegators; - - constructor (uint256 _validatorId, address _tokenAddress, address _stakingLogger, address _stakeManager) public { - validatorId = _validatorId; - token = ERC20(_tokenAddress); - stakingLogger = StakingInfo(_stakingLogger); - stakeManager = IStakeManager(_stakeManager); - } - - function udpateRewards(uint256 _reward, uint256 _totalStake) external returns(uint256); - function updateCommissionRate(uint256 newCommissionRate) external; - function withdrawRewardsValidator() external returns(uint256); - function exchangeRate() public view returns(uint256); - function buyVoucher(uint256 _amount) public; - function sellVoucher() public; - function withdrawRewards() public; - function unStakeClaimTokens() public; - function slash(uint256 slashRate, uint256 startEpoch, uint256 endEpoch) public; - // function _slashActive() internal {} - // function _slashInActive() internal {} - + using SafeMath for uint256; + ERC20 public token; + StakingInfo public stakingLogger; + IStakeManager public stakeManager; + uint256 public validatorId; + uint256 public validatorRewards; + uint256 public commissionRate; + uint256 public validatorDelegatorRatio = 10; + + uint256 public totalStake; + uint256 public rewards; + uint256 public activeAmount; + bool public delegation = true; + + struct Delegator { + uint256 amount; + uint256 withdrawEpoch; + } + + mapping(address => uint256) public amountStaked; + mapping(address => Delegator) public delegators; + + constructor( + uint256 _validatorId, + address _tokenAddress, + address _stakingLogger, + address _stakeManager + ) public { + validatorId = _validatorId; + token = ERC20(_tokenAddress); + stakingLogger = StakingInfo(_stakingLogger); + stakeManager = IStakeManager(_stakeManager); + } + + function udpateRewards(uint256 _reward, uint256 _totalStake) + external + returns (uint256); + function updateCommissionRate(uint256 newCommissionRate) external; + function withdrawRewardsValidator() external returns (uint256); + function exchangeRate() public view returns (uint256); + function buyVoucher(uint256 _amount) public; + function sellVoucher() public; + function withdrawRewards() public; + function unStakeClaimTokens() public; + function slash(uint256 slashRate, uint256 startEpoch, uint256 endEpoch) + public; + // function _slashActive() internal {} + // function _slashInActive() internal {} } diff --git a/contracts/staking/SlashingManager.sol b/contracts/staking/SlashingManager.sol index 21ef44f4b..ba52321b1 100644 --- a/contracts/staking/SlashingManager.sol +++ b/contracts/staking/SlashingManager.sol @@ -1,53 +1,77 @@ pragma solidity ^0.5.2; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; -import { Ownable } from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; - -import { StakeManager } from "./StakeManager.sol"; -import { ECVerify } from "../common/lib/ECVerify.sol"; -import { Registry } from "../common/Registry.sol"; +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; +import {Ownable} from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import {StakeManager} from "./StakeManager.sol"; +import {ECVerify} from "../common/lib/ECVerify.sol"; +import {Registry} from "../common/Registry.sol"; contract SlashingManager is Ownable { - using ECVerify for bytes32; - using RLPReader for bytes; - using RLPReader for RLPReader.RLPItem; - - uint256 public checkpointHaltEpoch = 0; - uint256 public haltInterval = 50; // epoch - uint256 public slashingRate = 5; // slashing % - uint256 public jailCheckpoints = 5; // checkpoints - bytes32 public chain = keccak256("heimdall-P5rXwg"); - uint256 public roundType = 2; - uint8 public voteType = 2; - Registry public registry; - - constructor (address _registry) public { - registry = Registry(_registry); - } - - function doubleSign(bytes memory vote1, bytes memory vote2, bytes memory sig1, bytes memory sig2) public { - // Todo: fix signer chanage for same validator - // Height/checkpoint for slashing - RLPReader.RLPItem[] memory dataList1 = vote1.toRlpItem().toList(); - RLPReader.RLPItem[] memory dataList2 = vote2.toRlpItem().toList(); - - require(dataList1[2].toUint() == dataList2[2].toUint(), "sig isn't duplicate"); - require((keccak256(dataList1[0].toBytes()) == chain && keccak256(dataList2[0].toBytes()) == chain),"Chain ID not same"); - require(dataList1[1].toUint() == roundType && dataList2[1].toUint() == roundType, "Round type not same "); - require((dataList1[3].toUint() == voteType && dataList2[3].toUint() == voteType), "Vote type not same"); - require(keccak256(dataList1[4].toBytes()) != keccak256(dataList2[4].toBytes()), "same vote"); - - address signer = keccak256(vote1).ecrecovery(sig1); - require(signer == keccak256(vote2).ecrecovery(sig2)); - // fetching validatorId is unnessacary but just to keep universal interface - // slash is called with validatorId - StakeManager stakeManager = StakeManager(registry.getStakeManagerAddress()); - uint256 validatorId = stakeManager.signerToValidator(signer); - stakeManager.slash(validatorId, slashingRate, jailCheckpoints); - } - - function checkpointHalt(uint256 start) public { - } + using ECVerify for bytes32; + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + + uint256 public checkpointHaltEpoch = 0; + uint256 public haltInterval = 50; // epoch + uint256 public slashingRate = 5; // slashing % + uint256 public jailCheckpoints = 5; // checkpoints + bytes32 public chain = keccak256("heimdall-P5rXwg"); + uint256 public roundType = 2; + uint8 public voteType = 2; + Registry public registry; + + constructor(address _registry) public { + registry = Registry(_registry); + } + + function doubleSign( + bytes memory vote1, + bytes memory vote2, + bytes memory sig1, + bytes memory sig2 + ) public { + // Todo: fix signer chanage for same validator + // Height/checkpoint for slashing + RLPReader.RLPItem[] memory dataList1 = vote1.toRlpItem().toList(); + RLPReader.RLPItem[] memory dataList2 = vote2.toRlpItem().toList(); + + require( + dataList1[2].toUint() == dataList2[2].toUint(), + "sig isn't duplicate" + ); + require( + (keccak256(dataList1[0].toBytes()) == chain && + keccak256(dataList2[0].toBytes()) == chain), + "Chain ID not same" + ); + require( + dataList1[1].toUint() == roundType && + dataList2[1].toUint() == roundType, + "Round type not same " + ); + require( + (dataList1[3].toUint() == voteType && + dataList2[3].toUint() == voteType), + "Vote type not same" + ); + require( + keccak256(dataList1[4].toBytes()) != + keccak256(dataList2[4].toBytes()), + "same vote" + ); + + address signer = keccak256(vote1).ecrecovery(sig1); + require(signer == keccak256(vote2).ecrecovery(sig2)); + // fetching validatorId is unnessacary but just to keep universal interface + // slash is called with validatorId + StakeManager stakeManager = StakeManager( + registry.getStakeManagerAddress() + ); + uint256 validatorId = stakeManager.signerToValidator(signer); + stakeManager.slash(validatorId, slashingRate, jailCheckpoints); + } + + function checkpointHalt(uint256 start) public {} } diff --git a/contracts/staking/StakeManager.sol b/contracts/staking/StakeManager.sol index de8c5103b..9324f5e7a 100644 --- a/contracts/staking/StakeManager.sol +++ b/contracts/staking/StakeManager.sol @@ -1,575 +1,763 @@ pragma solidity ^0.5.2; -import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import { ERC721Full } from "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol"; -import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; -import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; - -import { BytesLib } from "../common/lib/BytesLib.sol"; -import { ECVerify } from "../common/lib/ECVerify.sol"; -import { Merkle } from "../common/lib/Merkle.sol"; -import { Lockable } from "../common/mixin/Lockable.sol"; -import { RootChainable } from "../common/mixin/RootChainable.sol"; -import { Registry } from "../common/Registry.sol"; -import { IStakeManager } from "./IStakeManager.sol"; -import { ValidatorShare } from "./ValidatorShare.sol"; -import { StakingInfo } from "./StakingInfo.sol"; +import {IERC20} from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol"; +import {Math} from "openzeppelin-solidity/contracts/math/Math.sol"; +import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +import {BytesLib} from "../common/lib/BytesLib.sol"; +import {ECVerify} from "../common/lib/ECVerify.sol"; +import {Merkle} from "../common/lib/Merkle.sol"; +import {Lockable} from "../common/mixin/Lockable.sol"; +import {RootChainable} from "../common/mixin/RootChainable.sol"; +import {Registry} from "../common/Registry.sol"; +import {IStakeManager} from "./IStakeManager.sol"; +import {ValidatorShare} from "./ValidatorShare.sol"; +import {StakingInfo} from "./StakingInfo.sol"; contract ValidatorShareFactory { - /** + /** - factory to create new validatorShare contracts */ - function create(uint256 validatorId, address tokenAddress, address loggerAddress) public returns(address) { - ValidatorShare validatorShare = new ValidatorShare(validatorId, tokenAddress, loggerAddress, msg.sender); - validatorShare.transferOwnership(msg.sender); - return address(validatorShare); - } + function create( + uint256 validatorId, + address tokenAddress, + address loggerAddress + ) public returns (address) { + ValidatorShare validatorShare = new ValidatorShare( + validatorId, + tokenAddress, + loggerAddress, + msg.sender + ); + validatorShare.transferOwnership(msg.sender); + return address(validatorShare); + } } contract StakeManager is IStakeManager, ERC721Full, RootChainable, Lockable { - using SafeMath for uint256; - using ECVerify for bytes32; - using Merkle for bytes32; - - IERC20 public token; - address public registry; - StakingInfo public logger; - ValidatorShareFactory public factory; - // genesis/governance variables - uint256 public dynasty = 2**13; // unit: epoch 50 days - uint256 public CHECKPOINT_REWARD = 10000 * (10**18); // @todo update according to Chain - uint256 public MIN_DEPOSIT_SIZE = (10**18); // in ERC20 token - uint256 public EPOCH_LENGTH = 256; // unit : block - uint256 public UNSTAKE_DELAY = dynasty.mul(2); // unit: epoch - uint256 public checkPointBlockInterval = 255; - - // TODO: add events and gov. based update function - uint256 public proposerToSignerRewards = 10; // will be used with fraud proof - - uint256 public validatorThreshold = 10; //128 - uint256 public minLockInPeriod = 2; // unit: dynasty - uint256 public totalStaked; - uint256 public NFTCounter = 1; - uint256 public totalRewards; - uint256 public totalRewardsLiquidated; - uint256 public auctionPeriod = dynasty.div(4); // 1 week in epochs - bytes32 public accountStateRoot; - - // on dynasty update certain amount of cooldown period where there is no validator auction - uint256 replacementCoolDown; - - struct Auction { - uint256 amount; - uint256 startEpoch; - address user; - } - - struct State { - int256 amount; - int256 stakerCount; - } - - // signer to Validator mapping - mapping (address => uint256) public signerToValidator; - // validator metadata - // mapping (uint256 => Validator) public validators; - //Mapping for epoch to totalStake for that epoch - mapping (uint256 => State) public validatorState; - //Ongoing auctions for validatorId - mapping (uint256 => Auction) public validatorAuction; - - - uint256 public totalHeimdallFee; - - constructor (address _registry, address _rootchain, address _stakingLogger, address _ValidatorShareFactory) ERC721Full("Matic Validator", "MV") public { - registry = _registry; - rootChain = _rootchain; - logger = StakingInfo(_stakingLogger); - factory = ValidatorShareFactory(_ValidatorShareFactory); - } - - modifier onlyStaker(uint256 validatorId) { - require(ownerOf(validatorId) == msg.sender); - _; - } - - modifier onlySlashingMananger() { - require(Registry(registry).getSlashingManagerAddress() == msg.sender); - _; - } - - // TopUp heimdall fee - function topUpForFee(uint256 validatorId, uint256 heimdallFee) public { - require(heimdallFee >= MIN_DEPOSIT_SIZE, "Minimum amount is 1 Matic"); - require(token.transferFrom(msg.sender, address(this), heimdallFee), "Transfer stake failed"); - _topUpForFee(validatorId, heimdallFee); - } - - function _topUpForFee(uint256 validatorId, uint256 amount) private { - totalHeimdallFee = totalHeimdallFee.add(amount); - logger.logTopUpFee(validatorId, amount); - } - - function _claimFee(uint256 validatorId, uint256 amount) private { - totalHeimdallFee = totalHeimdallFee.sub(amount); - logger.logClaimFee(validatorId, amount); - } - - function claimFee(uint256 validatorId, uint256 accumSlashedAmount, uint256 amount, uint256 index, bytes memory proof) public onlyStaker(validatorId) { - //Ignoring other params becuase rewards distribution is on chain - require(keccak256(abi.encodePacked(validatorId, amount, accumSlashedAmount)).checkMembership(index, accountStateRoot, proof), "Wrong acc proof"); - require(token.transfer(msg.sender, amount)); - _claimFee(validatorId, amount); - } - - function stake(uint256 amount, uint256 heimdallFee, address signer, bool isContract) external { - stakeFor(msg.sender, amount, heimdallFee, signer, isContract); - } - - function totalStakedFor(address user) external view returns (uint256) { - if (user == address(0x0) || balanceOf(user) == 0) { - return 0; - } - return validators[tokenOfOwnerByIndex(user, 0)].amount; - } - - function supportsHistory() external pure returns (bool) { - return false; - } - - function startAuction(uint256 validatorId, uint256 amount) external { - require(isValidator(validatorId)); - // when dynasty period is updated validators are in cool down period - require(replacementCoolDown == 0 || replacementCoolDown <= currentEpoch, "Cool down period"); - require(auctionPeriod >= currentEpoch.sub(validatorAuction[validatorId].startEpoch), "Invalid auction period"); - // (dynasty--auctionPeriod)--(dynasty--auctionPeriod)--(dynasty--auctionPeriod) - // if it's auctionPeriod then will get residue from (CurrentPeriod of validator )%(dynasty--auctionPeriod) - // make sure that its `auctionPeriod` window - // dynasty = 30, auctionPeriod = 7, activationEpoch = 1, currentEpoch = 39 - // residue 1 = (39-1)% (30+7), if residue-dynasty > 0 it's `auctionPeriod` - require((currentEpoch.sub(validators[validatorId].activationEpoch) % dynasty.add(auctionPeriod)) > dynasty, "Not an auction time"); - - require(token.transferFrom(msg.sender, address(this), amount), "Transfer amount failed"); - - uint256 perceivedStake = validators[validatorId].amount.mul(perceivedStakeFactor(validatorId)); - perceivedStake = Math.max(perceivedStake, validatorAuction[validatorId].amount); - - require(perceivedStake < amount, "Must bid higher amount"); - - // create new auction - if (validatorAuction[validatorId].amount == 0) { - validatorAuction[validatorId] = Auction({ - amount: amount, - startEpoch: currentEpoch, - user: msg.sender - }); - } else { //replace prev auction - Auction storage auction = validatorAuction[validatorId]; - require(token.transfer(auction.user, auction.amount)); - auction.amount = amount; - auction.user = msg.sender; - } - logger.logStartAuction(validatorId, validators[validatorId].amount, validatorAuction[validatorId].amount); - } - - function confirmAuctionBid( - uint256 validatorId, - uint256 heimdallFee, /** for new validator */ - address signer, - bool isContract + using SafeMath for uint256; + using ECVerify for bytes32; + using Merkle for bytes32; + + IERC20 public token; + address public registry; + StakingInfo public logger; + ValidatorShareFactory public factory; + // genesis/governance variables + uint256 public dynasty = 2**13; // unit: epoch 50 days + uint256 public CHECKPOINT_REWARD = 10000 * (10**18); // @todo update according to Chain + uint256 public MIN_DEPOSIT_SIZE = (10**18); // in ERC20 token + uint256 public EPOCH_LENGTH = 256; // unit : block + uint256 public UNSTAKE_DELAY = dynasty.mul(2); // unit: epoch + uint256 public checkPointBlockInterval = 255; + + // TODO: add events and gov. based update function + uint256 public proposerToSignerRewards = 10; // will be used with fraud proof + + uint256 public validatorThreshold = 10; //128 + uint256 public minLockInPeriod = 2; // unit: dynasty + uint256 public totalStaked; + uint256 public NFTCounter = 1; + uint256 public totalRewards; + uint256 public totalRewardsLiquidated; + uint256 public auctionPeriod = dynasty.div(4); // 1 week in epochs + bytes32 public accountStateRoot; + + // on dynasty update certain amount of cooldown period where there is no validator auction + uint256 replacementCoolDown; + + struct Auction { + uint256 amount; + uint256 startEpoch; + address user; + } + + struct State { + int256 amount; + int256 stakerCount; + } + + // signer to Validator mapping + mapping(address => uint256) public signerToValidator; + // validator metadata + // mapping (uint256 => Validator) public validators; + //Mapping for epoch to totalStake for that epoch + mapping(uint256 => State) public validatorState; + //Ongoing auctions for validatorId + mapping(uint256 => Auction) public validatorAuction; + + uint256 public totalHeimdallFee; + + constructor( + address _registry, + address _rootchain, + address _stakingLogger, + address _ValidatorShareFactory + ) public ERC721Full("Matic Validator", "MV") { + registry = _registry; + rootChain = _rootchain; + logger = StakingInfo(_stakingLogger); + factory = ValidatorShareFactory(_ValidatorShareFactory); + } + + modifier onlyStaker(uint256 validatorId) { + require(ownerOf(validatorId) == msg.sender); + _; + } + + modifier onlySlashingMananger() { + require(Registry(registry).getSlashingManagerAddress() == msg.sender); + _; + } + + // TopUp heimdall fee + function topUpForFee(uint256 validatorId, uint256 heimdallFee) public { + require(heimdallFee >= MIN_DEPOSIT_SIZE, "Minimum amount is 1 Matic"); + require( + token.transferFrom(msg.sender, address(this), heimdallFee), + "Transfer stake failed" + ); + _topUpForFee(validatorId, heimdallFee); + } + + function _topUpForFee(uint256 validatorId, uint256 amount) private { + totalHeimdallFee = totalHeimdallFee.add(amount); + logger.logTopUpFee(validatorId, amount); + } + + function _claimFee(uint256 validatorId, uint256 amount) private { + totalHeimdallFee = totalHeimdallFee.sub(amount); + logger.logClaimFee(validatorId, amount); + } + + function claimFee( + uint256 validatorId, + uint256 accumSlashedAmount, + uint256 amount, + uint256 index, + bytes memory proof + ) public onlyStaker(validatorId) { + //Ignoring other params becuase rewards distribution is on chain + require( + keccak256(abi.encodePacked(validatorId, amount, accumSlashedAmount)) + .checkMembership(index, accountStateRoot, proof), + "Wrong acc proof" + ); + require(token.transfer(msg.sender, amount)); + _claimFee(validatorId, amount); + } + + function stake( + uint256 amount, + uint256 heimdallFee, + address signer, + bool isContract + ) external { + stakeFor(msg.sender, amount, heimdallFee, signer, isContract); + } + + function totalStakedFor(address user) external view returns (uint256) { + if (user == address(0x0) || balanceOf(user) == 0) { + return 0; + } + return validators[tokenOfOwnerByIndex(user, 0)].amount; + } + + function supportsHistory() external pure returns (bool) { + return false; + } + + function startAuction(uint256 validatorId, uint256 amount) external { + require(isValidator(validatorId)); + // when dynasty period is updated validators are in cool down period + require( + replacementCoolDown == 0 || replacementCoolDown <= currentEpoch, + "Cool down period" + ); + require( + auctionPeriod >= + currentEpoch.sub(validatorAuction[validatorId].startEpoch), + "Invalid auction period" + ); + // (dynasty--auctionPeriod)--(dynasty--auctionPeriod)--(dynasty--auctionPeriod) + // if it's auctionPeriod then will get residue from (CurrentPeriod of validator )%(dynasty--auctionPeriod) + // make sure that its `auctionPeriod` window + // dynasty = 30, auctionPeriod = 7, activationEpoch = 1, currentEpoch = 39 + // residue 1 = (39-1)% (30+7), if residue-dynasty > 0 it's `auctionPeriod` + require( + (currentEpoch.sub(validators[validatorId].activationEpoch) % + dynasty.add(auctionPeriod)) > + dynasty, + "Not an auction time" + ); + + require( + token.transferFrom(msg.sender, address(this), amount), + "Transfer amount failed" + ); + + uint256 perceivedStake = validators[validatorId].amount.mul( + perceivedStakeFactor(validatorId) + ); + perceivedStake = Math.max( + perceivedStake, + validatorAuction[validatorId].amount + ); + + require(perceivedStake < amount, "Must bid higher amount"); + + // create new auction + if (validatorAuction[validatorId].amount == 0) { + validatorAuction[validatorId] = Auction({ + amount: amount, + startEpoch: currentEpoch, + user: msg.sender + }); + } else { + //replace prev auction + Auction storage auction = validatorAuction[validatorId]; + require(token.transfer(auction.user, auction.amount)); + auction.amount = amount; + auction.user = msg.sender; + } + logger.logStartAuction( + validatorId, + validators[validatorId].amount, + validatorAuction[validatorId].amount + ); + } + + function confirmAuctionBid( + uint256 validatorId, + uint256 heimdallFee, /** for new validator */ + address signer, + bool isContract ) external onlyWhenUnlocked { - Auction storage auction = validatorAuction[validatorId]; - Validator storage validator = validators[validatorId]; - // require(auction.user == msg.sender);// any one can call confrimAuction - require(auctionPeriod.add(auction.startEpoch) <= currentEpoch, "Confirmation is not allowed before auctionPeriod"); - - // validator is last auctioner - if (auction.user == ownerOf(validatorId)) { - uint256 refund = validator.amount; - require(token.transfer(auction.user, refund)); - validator.amount = auction.amount; - - //cleanup auction data - auction.amount = 0; - auction.user = address(0x0); - auction.startEpoch = currentEpoch.add(dynasty); - //update total stake amount - totalStaked = totalStaked.add(validator.amount.sub(refund)); - logger.logStakeUpdate(validatorId); - logger.logConfirmAuction(validatorId, validatorId, validator.amount); - } else { - // dethrone - _unstake(validatorId, currentEpoch); - require(token.transferFrom(msg.sender, address(this), heimdallFee), "Transfer fee failed"); - _topUpForFee(NFTCounter, heimdallFee); - _stakeFor(auction.user, auction.amount, signer, isContract); - - logger.logConfirmAuction(NFTCounter.sub(1), validatorId, auction.amount); - delete validatorAuction[validatorId]; - } - } - - function unstake(uint256 validatorId) external onlyStaker(validatorId) { - require(validatorAuction[validatorId].amount == 0, "Wait for auction completion"); - uint256 exitEpoch = currentEpoch.add(1);// notice period - require(validators[validatorId].activationEpoch > 0 && - validators[validatorId].deactivationEpoch == 0 && - validators[validatorId].status == Status.Active); - _unstake(validatorId, exitEpoch); - } - - function delegationTransfer(uint256 validatorId, uint256 amount, address delegator) external { - require(validators[validatorId].contractAddress == msg.sender); - require(token.transfer(delegator, amount), "Insufficent rewards"); - } - - function stakeFor(address user, uint256 amount, uint256 heimdallFee, address signer, bool isContract) public onlyWhenUnlocked { - require(currentValidatorSetSize() < validatorThreshold); - require(balanceOf(user) == 0, "Only one time staking is allowed"); - require(amount > MIN_DEPOSIT_SIZE); - require(signerToValidator[signer] == 0); - - require(token.transferFrom(msg.sender, address(this), amount.add(heimdallFee)), "Transfer stake failed"); - // _topup - _topUpForFee(NFTCounter/** validatorId*/, heimdallFee); - _stakeFor(user, amount, signer, isContract); - } - - function perceivedStakeFactor(uint256 validatorId) public view returns(uint256){ - // TODO: use age, rewardRatio, and slashing/reward rate - return 1; - } - - function unstakeClaim(uint256 validatorId) public onlyStaker(validatorId) { - // can only claim stake back after WITHDRAWAL_DELAY - require(validators[validatorId].deactivationEpoch > 0 && - validators[validatorId].deactivationEpoch.add(WITHDRAWAL_DELAY) <= currentEpoch && - validators[validatorId].status != Status.Unstaked); - uint256 amount = validators[validatorId].amount; - totalStaked = totalStaked.sub(amount); - - // TODO :add slashing here use soft slashing in slash amt variable - _burn(validatorId); - delete signerToValidator[validators[validatorId].signer]; - // delete validators[validatorId]; - validators[validatorId].status = Status.Unstaked; - require(token.transfer(msg.sender, amount.add(validators[validatorId].reward)), "Transfer stake failed"); - logger.logUnstaked(msg.sender, validatorId, amount, totalStaked); - } - - // slashing and jail interface - function restake(uint256 validatorId, uint256 amount, bool stakeRewards) public onlyStaker(validatorId) { - require(validators[validatorId].deactivationEpoch < currentEpoch, "No use of restaking"); - - if (amount > 0) { - require(token.transferFrom(msg.sender, address(this), amount), "Transfer stake"); - } - if (stakeRewards) { - amount += validators[validatorId].reward; - validators[validatorId].reward = 0; - } - totalStaked = totalStaked.add(amount); - validators[validatorId].amount += amount; - validatorState[currentEpoch].amount = ( - validatorState[currentEpoch].amount + int256(amount)); - - logger.logStakeUpdate(validatorId); - logger.logReStaked(validatorId, validators[validatorId].amount, totalStaked); - } - - function withdrawRewards(uint256 validatorId) public onlyStaker(validatorId) { - uint256 amount = validators[validatorId].reward; - address _contract = validators[validatorId].contractAddress; - if (_contract != address(0x0)) { - amount = amount.add(ValidatorShare(_contract).withdrawRewardsValidator()); - } - totalRewardsLiquidated = totalRewardsLiquidated.add(amount); - validators[validatorId].reward = 0; - require(token.transfer(msg.sender, amount), "Insufficent rewards"); - logger.logClaimRewards(validatorId, amount, totalRewardsLiquidated); - } - - // if not jailed then in state of warning, else will be unstaking after x epoch - function slash(uint256 validatorId, uint256 slashingRate, uint256 jailCheckpoints) public onlySlashingMananger { - // if contract call contract.slash - // if (validators[validatorId].contractAddress != address(0x0)) { - // // ValidatorShare(validators[validatorId].contractAddress).slash(slashingRate, currentEpoch, currentEpoch); - // } - uint256 amount = validators[validatorId].amount.mul(slashingRate).div(100); - validators[validatorId].amount = validators[validatorId].amount.sub(amount); - if (validators[validatorId].amount < MIN_DEPOSIT_SIZE || jailCheckpoints > 0) { - jail(validatorId, jailCheckpoints); - } - // todo: slash event - logger.logStakeUpdate(validatorId); - } - - function unJail(uint256 validatorId) public onlyStaker(validatorId) { - require( - validators[validatorId].deactivationEpoch > currentEpoch && - validators[validatorId].jailTime <= currentEpoch && - validators[validatorId].status == Status.Locked); - - uint256 amount = validators[validatorId].amount; - require(amount >= MIN_DEPOSIT_SIZE); - uint256 exitEpoch = validators[validatorId].deactivationEpoch; - - int256 delegationAmount = 0; - if (validators[validatorId].contractAddress != address(0x0)) { - delegationAmount = int256(ValidatorShare(validators[validatorId].contractAddress).activeAmount()); - ValidatorShare(validators[validatorId].contractAddress).unlock(); - } - - // undo timline so that validator is normal validator - updateTimeLine(exitEpoch, (int256(amount) + delegationAmount ), 1); - - validators[validatorId].deactivationEpoch = 0; - validators[validatorId].status = Status.Active; - validators[validatorId].jailTime = 0; - } - - // in context of slashing - function jail(uint256 validatorId, uint256 jailCheckpoints) public /** only*/ { - // Todo: requires and more conditions - uint256 amount = validators[validatorId].amount; - // should unbond instantly - uint256 exitEpoch = currentEpoch.add(UNSTAKE_DELAY); // jail period - - int256 delegationAmount = 0; - validators[validatorId].jailTime = jailCheckpoints; - if (validators[validatorId].contractAddress != address(0x0)) { - delegationAmount = int256(ValidatorShare(validators[validatorId].contractAddress).activeAmount()); - ValidatorShare(validators[validatorId].contractAddress).lock(); - } - // update future in case of no `unJail` - updateTimeLine(exitEpoch, -(int256(amount) + delegationAmount ), -1); - validators[validatorId].deactivationEpoch = exitEpoch; - validators[validatorId].status = Status.Locked; - logger.logJailed(validatorId, exitEpoch); - } - - // returns valid validator for current epoch - function getCurrentValidatorSet() public view returns (uint256[] memory) { - uint256[] memory _validators = new uint256[](validatorThreshold); - uint256 validator; - uint256 k = 0; - for (uint256 i = 0; i < totalSupply() ; i++) { - validator = tokenByIndex(i); - if (isValidator(validator)) { - _validators[k++] = validator; - } - } - return _validators; - } - - function getValidatorId(address user) public view returns(uint256) { - return tokenOfOwnerByIndex(user, 0); - } - - // set staking Token - function setToken(address _token) public onlyOwner { - require(_token != address(0x0)); - token = IERC20(_token); - } - - // Change the number of validators required to allow a passed header root - function updateValidatorThreshold(uint256 newThreshold) public onlyOwner { - require(newThreshold > 0); - logger.logThresholdChange(newThreshold, validatorThreshold); - validatorThreshold = newThreshold; - } - - function updateCheckPointBlockInterval(uint256 _blocks) public onlyOwner { - require(_blocks > 0, "Blocks interval must be non-zero"); - checkPointBlockInterval = _blocks; - } - - // Change reward for each checkpoint - function updateCheckpointReward(uint256 newReward) public onlyOwner { - require(newReward > 0); - logger.logRewardUpdate(newReward, CHECKPOINT_REWARD); - CHECKPOINT_REWARD = newReward; - } - - function updateValidatorState(uint256 validatorId, int256 amount) public { - require(validators[validatorId].contractAddress == msg.sender); - // require(epoch >= currentEpoch, "Can't change past"); - validatorState[currentEpoch].amount = ( - validatorState[currentEpoch].amount + amount - ); - } - - function updateDynastyValue(uint256 newDynasty) public onlyOwner { - require(newDynasty > 0); - logger.logDynastyValueChange(newDynasty, dynasty); - dynasty = newDynasty; - UNSTAKE_DELAY = dynasty.div(2); - WITHDRAWAL_DELAY = dynasty.mul(2); - auctionPeriod = dynasty.div(4); - // set cool down period - replacementCoolDown = currentEpoch.add(auctionPeriod); - } - - function updateSigner(uint256 validatorId, address _signer) public onlyStaker(validatorId) { - require(_signer != address(0x0) && signerToValidator[_signer] == 0); - - // update signer event - logger.logSignerChange(validatorId, validators[validatorId].signer, _signer); - - delete signerToValidator[validators[validatorId].signer]; - signerToValidator[_signer] = validatorId; - validators[validatorId].signer = _signer; - } - - function updateMinLockInPeriod(uint256 epochs) public onlyOwner { - minLockInPeriod = epochs; - } - - function currentValidatorSetSize() public view returns (uint256) { - return uint256(validatorState[currentEpoch].stakerCount); - } - - function currentValidatorSetTotalStake() public view returns (uint256) { - return uint256(validatorState[currentEpoch].amount); - } - - function getValidatorContract(uint256 validatorId) public view returns(address) { - return validators[validatorId].contractAddress; - } - - function isValidator(uint256 validatorId) public view returns (bool) { - return ( - validators[validatorId].amount > 0 && - (validators[validatorId].activationEpoch != 0 && - validators[validatorId].activationEpoch <= currentEpoch ) && - (validators[validatorId].deactivationEpoch == 0 || - validators[validatorId].deactivationEpoch > currentEpoch) && - validators[validatorId].status == Status.Active // Todo: reduce logic - ); - } - - function checkSignatures(uint256 blockInterval, bytes32 voteHash, bytes32 stateRoot, bytes memory sigs) public onlyRootChain returns(uint256) { - // checkpoint rewards are based on BlockInterval multiplied on `CHECKPOINT_REWARD` - // for bigger checkpoints reward is capped at `CHECKPOINT_REWARD` - // if interval is 50% of checkPointBlockInterval then reward R is half of `CHECKPOINT_REWARD` - // and then stakePower is 90% of currentValidatorSetTotalStake then final reward is 90% of R - uint256 _reward = blockInterval.mul(CHECKPOINT_REWARD).div(checkPointBlockInterval); - _reward = Math.min(CHECKPOINT_REWARD, _reward); - - uint256 stakePower = currentValidatorSetTotalStake(); - // update stateMerkleTree root for accounts balance on heimdall chain - accountStateRoot = stateRoot; - _finalizeCommit(); - return checkSignature(stakePower, _reward, voteHash, sigs); - } - - function checkSignature(uint256 stakePower, uint256 _reward, bytes32 voteHash, bytes memory sigs) internal returns(uint256) { - // total voting power - uint256 _stakePower; - //TODO: add proposer bonus - address lastAdd; // cannot have address(0x0) as an owner - for (uint64 i = 0; i < sigs.length; i += 65) { - bytes memory sigElement = BytesLib.slice(sigs, i, 65); - address signer = voteHash.ecrecovery(sigElement); - - uint256 validatorId = signerToValidator[signer]; - // check if signer is staker and not proposer - if (signer == lastAdd) { - break; - } - else if ( - isValidator(validatorId) && - signer > lastAdd - ) { - lastAdd = signer; + Auction storage auction = validatorAuction[validatorId]; Validator storage validator = validators[validatorId]; - uint256 valPow; - // add delegation power - if (validator.contractAddress != address(0x0)) { - valPow = ValidatorShare(validator.contractAddress).udpateRewards( _reward, stakePower); - } else { - //TODO: check for div leaks in rewards amount - valPow = validator.amount; - validator.reward = validator.reward.add(valPow.mul(_reward).div(stakePower)); + // require(auction.user == msg.sender);// any one can call confrimAuction + require( + auctionPeriod.add(auction.startEpoch) <= currentEpoch, + "Confirmation is not allowed before auctionPeriod" + ); + + // validator is last auctioner + if (auction.user == ownerOf(validatorId)) { + uint256 refund = validator.amount; + require(token.transfer(auction.user, refund)); + validator.amount = auction.amount; + + //cleanup auction data + auction.amount = 0; + auction.user = address(0x0); + auction.startEpoch = currentEpoch.add(dynasty); + //update total stake amount + totalStaked = totalStaked.add(validator.amount.sub(refund)); + logger.logStakeUpdate(validatorId); + logger.logConfirmAuction( + validatorId, + validatorId, + validator.amount + ); + } else { + // dethrone + _unstake(validatorId, currentEpoch); + require( + token.transferFrom(msg.sender, address(this), heimdallFee), + "Transfer fee failed" + ); + _topUpForFee(NFTCounter, heimdallFee); + _stakeFor(auction.user, auction.amount, signer, isContract); + + logger.logConfirmAuction( + NFTCounter.sub(1), + validatorId, + auction.amount + ); + delete validatorAuction[validatorId]; + } + } + + function unstake(uint256 validatorId) external onlyStaker(validatorId) { + require( + validatorAuction[validatorId].amount == 0, + "Wait for auction completion" + ); + uint256 exitEpoch = currentEpoch.add(1); // notice period + require( + validators[validatorId].activationEpoch > 0 && + validators[validatorId].deactivationEpoch == 0 && + validators[validatorId].status == Status.Active + ); + _unstake(validatorId, exitEpoch); + } + + function delegationTransfer( + uint256 validatorId, + uint256 amount, + address delegator + ) external { + require(validators[validatorId].contractAddress == msg.sender); + require(token.transfer(delegator, amount), "Insufficent rewards"); + } + + function stakeFor( + address user, + uint256 amount, + uint256 heimdallFee, + address signer, + bool isContract + ) public onlyWhenUnlocked { + require(currentValidatorSetSize() < validatorThreshold); + require(balanceOf(user) == 0, "Only one time staking is allowed"); + require(amount > MIN_DEPOSIT_SIZE); + require(signerToValidator[signer] == 0); + + require( + token.transferFrom( + msg.sender, + address(this), + amount.add(heimdallFee) + ), + "Transfer stake failed" + ); + // _topup + _topUpForFee( + NFTCounter, /** validatorId*/ + heimdallFee + ); + _stakeFor(user, amount, signer, isContract); + } + + function perceivedStakeFactor(uint256 validatorId) + public + view + returns (uint256) + { + // TODO: use age, rewardRatio, and slashing/reward rate + return 1; + } + + function unstakeClaim(uint256 validatorId) public onlyStaker(validatorId) { + // can only claim stake back after WITHDRAWAL_DELAY + require( + validators[validatorId].deactivationEpoch > 0 && + validators[validatorId].deactivationEpoch.add( + WITHDRAWAL_DELAY + ) <= + currentEpoch && + validators[validatorId].status != Status.Unstaked + ); + uint256 amount = validators[validatorId].amount; + totalStaked = totalStaked.sub(amount); + + // TODO :add slashing here use soft slashing in slash amt variable + _burn(validatorId); + delete signerToValidator[validators[validatorId].signer]; + // delete validators[validatorId]; + validators[validatorId].status = Status.Unstaked; + require( + token.transfer( + msg.sender, + amount.add(validators[validatorId].reward) + ), + "Transfer stake failed" + ); + logger.logUnstaked(msg.sender, validatorId, amount, totalStaked); + } + + // slashing and jail interface + function restake(uint256 validatorId, uint256 amount, bool stakeRewards) + public + onlyStaker(validatorId) + { + require( + validators[validatorId].deactivationEpoch < currentEpoch, + "No use of restaking" + ); + + if (amount > 0) { + require( + token.transferFrom(msg.sender, address(this), amount), + "Transfer stake" + ); + } + if (stakeRewards) { + amount += validators[validatorId].reward; + validators[validatorId].reward = 0; + } + totalStaked = totalStaked.add(amount); + validators[validatorId].amount += amount; + validatorState[currentEpoch].amount = (validatorState[currentEpoch] + .amount + + int256(amount)); + + logger.logStakeUpdate(validatorId); + logger.logReStaked( + validatorId, + validators[validatorId].amount, + totalStaked + ); + } + + function withdrawRewards(uint256 validatorId) + public + onlyStaker(validatorId) + { + uint256 amount = validators[validatorId].reward; + address _contract = validators[validatorId].contractAddress; + if (_contract != address(0x0)) { + amount = amount.add( + ValidatorShare(_contract).withdrawRewardsValidator() + ); + } + totalRewardsLiquidated = totalRewardsLiquidated.add(amount); + validators[validatorId].reward = 0; + require(token.transfer(msg.sender, amount), "Insufficent rewards"); + logger.logClaimRewards(validatorId, amount, totalRewardsLiquidated); + } + + // if not jailed then in state of warning, else will be unstaking after x epoch + function slash( + uint256 validatorId, + uint256 slashingRate, + uint256 jailCheckpoints + ) public onlySlashingMananger { + // if contract call contract.slash + // if (validators[validatorId].contractAddress != address(0x0)) { + // // ValidatorShare(validators[validatorId].contractAddress).slash(slashingRate, currentEpoch, currentEpoch); + // } + uint256 amount = validators[validatorId].amount.mul(slashingRate).div( + 100 + ); + validators[validatorId].amount = validators[validatorId].amount.sub( + amount + ); + if ( + validators[validatorId].amount < MIN_DEPOSIT_SIZE || + jailCheckpoints > 0 + ) { + jail(validatorId, jailCheckpoints); + } + // todo: slash event + logger.logStakeUpdate(validatorId); + } + + function unJail(uint256 validatorId) public onlyStaker(validatorId) { + require( + validators[validatorId].deactivationEpoch > currentEpoch && + validators[validatorId].jailTime <= currentEpoch && + validators[validatorId].status == Status.Locked + ); + + uint256 amount = validators[validatorId].amount; + require(amount >= MIN_DEPOSIT_SIZE); + uint256 exitEpoch = validators[validatorId].deactivationEpoch; + + int256 delegationAmount = 0; + if (validators[validatorId].contractAddress != address(0x0)) { + delegationAmount = int256( + ValidatorShare(validators[validatorId].contractAddress) + .activeAmount() + ); + ValidatorShare(validators[validatorId].contractAddress).unlock(); + } + + // undo timline so that validator is normal validator + updateTimeLine(exitEpoch, (int256(amount) + delegationAmount), 1); + + validators[validatorId].deactivationEpoch = 0; + validators[validatorId].status = Status.Active; + validators[validatorId].jailTime = 0; + } + + // in context of slashing + function jail(uint256 validatorId, uint256 jailCheckpoints) + public + /** only*/ + { + // Todo: requires and more conditions + uint256 amount = validators[validatorId].amount; + // should unbond instantly + uint256 exitEpoch = currentEpoch.add(UNSTAKE_DELAY); // jail period + + int256 delegationAmount = 0; + validators[validatorId].jailTime = jailCheckpoints; + if (validators[validatorId].contractAddress != address(0x0)) { + delegationAmount = int256( + ValidatorShare(validators[validatorId].contractAddress) + .activeAmount() + ); + ValidatorShare(validators[validatorId].contractAddress).lock(); + } + // update future in case of no `unJail` + updateTimeLine(exitEpoch, -(int256(amount) + delegationAmount), -1); + validators[validatorId].deactivationEpoch = exitEpoch; + validators[validatorId].status = Status.Locked; + logger.logJailed(validatorId, exitEpoch); + } + + // returns valid validator for current epoch + function getCurrentValidatorSet() public view returns (uint256[] memory) { + uint256[] memory _validators = new uint256[](validatorThreshold); + uint256 validator; + uint256 k = 0; + for (uint256 i = 0; i < totalSupply(); i++) { + validator = tokenByIndex(i); + if (isValidator(validator)) { + _validators[k++] = validator; + } + } + return _validators; + } + + function getValidatorId(address user) public view returns (uint256) { + return tokenOfOwnerByIndex(user, 0); + } + + // set staking Token + function setToken(address _token) public onlyOwner { + require(_token != address(0x0)); + token = IERC20(_token); + } + + // Change the number of validators required to allow a passed header root + function updateValidatorThreshold(uint256 newThreshold) public onlyOwner { + require(newThreshold > 0); + logger.logThresholdChange(newThreshold, validatorThreshold); + validatorThreshold = newThreshold; + } + + function updateCheckPointBlockInterval(uint256 _blocks) public onlyOwner { + require(_blocks > 0, "Blocks interval must be non-zero"); + checkPointBlockInterval = _blocks; + } + + // Change reward for each checkpoint + function updateCheckpointReward(uint256 newReward) public onlyOwner { + require(newReward > 0); + logger.logRewardUpdate(newReward, CHECKPOINT_REWARD); + CHECKPOINT_REWARD = newReward; + } + + function updateValidatorState(uint256 validatorId, int256 amount) public { + require(validators[validatorId].contractAddress == msg.sender); + // require(epoch >= currentEpoch, "Can't change past"); + validatorState[currentEpoch].amount = (validatorState[currentEpoch] + .amount + + amount); + } + + function updateDynastyValue(uint256 newDynasty) public onlyOwner { + require(newDynasty > 0); + logger.logDynastyValueChange(newDynasty, dynasty); + dynasty = newDynasty; + UNSTAKE_DELAY = dynasty.div(2); + WITHDRAWAL_DELAY = dynasty.mul(2); + auctionPeriod = dynasty.div(4); + // set cool down period + replacementCoolDown = currentEpoch.add(auctionPeriod); + } + + function updateSigner(uint256 validatorId, address _signer) + public + onlyStaker(validatorId) + { + require(_signer != address(0x0) && signerToValidator[_signer] == 0); + + // update signer event + logger.logSignerChange( + validatorId, + validators[validatorId].signer, + _signer + ); + + delete signerToValidator[validators[validatorId].signer]; + signerToValidator[_signer] = validatorId; + validators[validatorId].signer = _signer; + } + + function updateMinLockInPeriod(uint256 epochs) public onlyOwner { + minLockInPeriod = epochs; + } + + function currentValidatorSetSize() public view returns (uint256) { + return uint256(validatorState[currentEpoch].stakerCount); + } + + function currentValidatorSetTotalStake() public view returns (uint256) { + return uint256(validatorState[currentEpoch].amount); + } + + function getValidatorContract(uint256 validatorId) + public + view + returns (address) + { + return validators[validatorId].contractAddress; + } + + function isValidator(uint256 validatorId) public view returns (bool) { + return (validators[validatorId].amount > 0 && + (validators[validatorId].activationEpoch != 0 && + validators[validatorId].activationEpoch <= currentEpoch) && + (validators[validatorId].deactivationEpoch == 0 || + validators[validatorId].deactivationEpoch > currentEpoch) && + validators[validatorId].status == Status.Active); // Todo: reduce logic + } + + function checkSignatures( + uint256 blockInterval, + bytes32 voteHash, + bytes32 stateRoot, + bytes memory sigs + ) public onlyRootChain returns (uint256) { + // checkpoint rewards are based on BlockInterval multiplied on `CHECKPOINT_REWARD` + // for bigger checkpoints reward is capped at `CHECKPOINT_REWARD` + // if interval is 50% of checkPointBlockInterval then reward R is half of `CHECKPOINT_REWARD` + // and then stakePower is 90% of currentValidatorSetTotalStake then final reward is 90% of R + uint256 _reward = blockInterval.mul(CHECKPOINT_REWARD).div( + checkPointBlockInterval + ); + _reward = Math.min(CHECKPOINT_REWARD, _reward); + + uint256 stakePower = currentValidatorSetTotalStake(); + // update stateMerkleTree root for accounts balance on heimdall chain + accountStateRoot = stateRoot; + _finalizeCommit(); + return checkSignature(stakePower, _reward, voteHash, sigs); + } + + function checkSignature( + uint256 stakePower, + uint256 _reward, + bytes32 voteHash, + bytes memory sigs + ) internal returns (uint256) { + // total voting power + uint256 _stakePower; + //TODO: add proposer bonus + address lastAdd; // cannot have address(0x0) as an owner + for (uint64 i = 0; i < sigs.length; i += 65) { + bytes memory sigElement = BytesLib.slice(sigs, i, 65); + address signer = voteHash.ecrecovery(sigElement); + + uint256 validatorId = signerToValidator[signer]; + // check if signer is staker and not proposer + if (signer == lastAdd) { + break; + } else if (isValidator(validatorId) && signer > lastAdd) { + lastAdd = signer; + Validator storage validator = validators[validatorId]; + uint256 valPow; + // add delegation power + if (validator.contractAddress != address(0x0)) { + valPow = ValidatorShare(validator.contractAddress) + .udpateRewards(_reward, stakePower); + } else { + //TODO: check for div leaks in rewards amount + valPow = validator.amount; + validator.reward = validator.reward.add( + valPow.mul(_reward).div(stakePower) + ); + } + _stakePower = _stakePower.add(valPow); + } } - _stakePower = _stakePower.add(valPow); - } - } - - _reward = CHECKPOINT_REWARD.mul(_stakePower).div(currentValidatorSetTotalStake()); - totalRewards = totalRewards.add(_reward); - require(_stakePower >= currentValidatorSetTotalStake().mul(2).div(3).add(1)); - return _reward; - } - - function challangeStateRootUpdate(bytes memory checkpointTx /* txData from submitCheckpoint */) public { - // TODO: check for 2/3+1 sig and validate non-inclusion in newStateUpdate - // UPDATE: since we've moved rewards to on chain there is no urgent need for this function - // becuase heimdall fee can be trusted on 2/3+1 security - } - - function _stakeFor(address user, uint256 amount, address signer, bool isContract) internal { - totalStaked = totalStaked.add(amount); - - validators[NFTCounter] = Validator({ - reward: 0, - amount: amount, - activationEpoch: currentEpoch, - deactivationEpoch: 0, - jailTime: 0, - signer: signer, - contractAddress: isContract ? factory.create(NFTCounter, address(token), address(logger)) : address(0x0), - status : Status.Active - }); - - _mint(user, NFTCounter); - - signerToValidator[signer] = NFTCounter; - updateTimeLine(currentEpoch, int256(amount), 1); - // no Auctions for 1 dynasty - validatorAuction[NFTCounter].startEpoch = currentEpoch.add(dynasty); - logger.logStaked(signer, NFTCounter, currentEpoch, amount, totalStaked); - NFTCounter = NFTCounter.add(1); - } - - function _unstake(uint256 validatorId, uint256 exitEpoch) internal { - //Todo: add state here consider jail - uint256 amount = validators[validatorId].amount; - - validators[validatorId].deactivationEpoch = exitEpoch; - - // unbond all delegators in future - int256 delegationAmount = 0; - if (validators[validatorId].contractAddress != address(0x0)) { - delegationAmount = int256(ValidatorShare(validators[validatorId].contractAddress).activeAmount()); - ValidatorShare(validators[validatorId].contractAddress).lock(); - } - - // update future - updateTimeLine(exitEpoch, -(int256(amount) + delegationAmount), -1); - - logger.logUnstakeInit(msg.sender, validatorId, exitEpoch, amount); - } - - function _finalizeCommit() internal { - uint256 nextEpoch = currentEpoch.add(1); - // update totalstake and validator count - validatorState[nextEpoch].amount = ( - validatorState[currentEpoch].amount + validatorState[nextEpoch].amount - ); - validatorState[nextEpoch].stakerCount = ( - validatorState[currentEpoch].stakerCount + validatorState[nextEpoch].stakerCount - ); - - // erase old data/history - delete validatorState[currentEpoch]; - currentEpoch = nextEpoch; - } - - function updateTimeLine(uint256 epoch, int256 amount, int256 stakerCount) private { - validatorState[epoch].amount += amount; - validatorState[epoch].stakerCount += stakerCount; - } + + _reward = CHECKPOINT_REWARD.mul(_stakePower).div( + currentValidatorSetTotalStake() + ); + totalRewards = totalRewards.add(_reward); + require( + _stakePower >= currentValidatorSetTotalStake().mul(2).div(3).add(1) + ); + return _reward; + } + + function challangeStateRootUpdate( + bytes memory checkpointTx /* txData from submitCheckpoint */ + ) public { + // TODO: check for 2/3+1 sig and validate non-inclusion in newStateUpdate + // UPDATE: since we've moved rewards to on chain there is no urgent need for this function + // becuase heimdall fee can be trusted on 2/3+1 security + } + + function _stakeFor( + address user, + uint256 amount, + address signer, + bool isContract + ) internal { + totalStaked = totalStaked.add(amount); + + validators[NFTCounter] = Validator({ + reward: 0, + amount: amount, + activationEpoch: currentEpoch, + deactivationEpoch: 0, + jailTime: 0, + signer: signer, + contractAddress: isContract + ? factory.create(NFTCounter, address(token), address(logger)) + : address(0x0), + status: Status.Active + }); + + _mint(user, NFTCounter); + + signerToValidator[signer] = NFTCounter; + updateTimeLine(currentEpoch, int256(amount), 1); + // no Auctions for 1 dynasty + validatorAuction[NFTCounter].startEpoch = currentEpoch.add(dynasty); + logger.logStaked(signer, NFTCounter, currentEpoch, amount, totalStaked); + NFTCounter = NFTCounter.add(1); + } + + function _unstake(uint256 validatorId, uint256 exitEpoch) internal { + //Todo: add state here consider jail + uint256 amount = validators[validatorId].amount; + + validators[validatorId].deactivationEpoch = exitEpoch; + + // unbond all delegators in future + int256 delegationAmount = 0; + if (validators[validatorId].contractAddress != address(0x0)) { + delegationAmount = int256( + ValidatorShare(validators[validatorId].contractAddress) + .activeAmount() + ); + ValidatorShare(validators[validatorId].contractAddress).lock(); + } + + // update future + updateTimeLine(exitEpoch, -(int256(amount) + delegationAmount), -1); + + logger.logUnstakeInit(msg.sender, validatorId, exitEpoch, amount); + } + + function _finalizeCommit() internal { + uint256 nextEpoch = currentEpoch.add(1); + // update totalstake and validator count + validatorState[nextEpoch].amount = (validatorState[currentEpoch] + .amount + + validatorState[nextEpoch].amount); + validatorState[nextEpoch].stakerCount = (validatorState[currentEpoch] + .stakerCount + + validatorState[nextEpoch].stakerCount); + + // erase old data/history + delete validatorState[currentEpoch]; + currentEpoch = nextEpoch; + } + + function updateTimeLine(uint256 epoch, int256 amount, int256 stakerCount) + private + { + validatorState[epoch].amount += amount; + validatorState[epoch].stakerCount += stakerCount; + } } diff --git a/contracts/staking/StakingInfo.sol b/contracts/staking/StakingInfo.sol index 3b53eb38f..b7f42e656 100644 --- a/contracts/staking/StakingInfo.sol +++ b/contracts/staking/StakingInfo.sol @@ -1,175 +1,348 @@ pragma solidity ^0.5.2; -import { Registry } from "../common/Registry.sol"; - +import {Registry} from "../common/Registry.sol"; // dummy interface to avoid cyclic dependency contract IStakeManager { - enum Status { Inactive, Active, Locked, Unstaked } - - struct Validator { - uint256 amount; - uint256 reward; - uint256 activationEpoch; - uint256 deactivationEpoch; - uint256 jailTime; - address signer; - address contractAddress; - Status status; - } - - mapping (uint256 => Validator) public validators; - bytes32 public accountStateRoot; - uint256 public activeAmount;// delegation amount from validator contract + enum Status {Inactive, Active, Locked, Unstaked} + + struct Validator { + uint256 amount; + uint256 reward; + uint256 activationEpoch; + uint256 deactivationEpoch; + uint256 jailTime; + address signer; + address contractAddress; + Status status; + } + + mapping(uint256 => Validator) public validators; + bytes32 public accountStateRoot; + uint256 public activeAmount; // delegation amount from validator contract } - contract StakingInfo { - event Staked(address indexed signer, uint256 indexed validatorId, uint256 indexed activationEpoch, uint256 amount, uint256 total); - event Unstaked(address indexed user, uint256 indexed validatorId, uint256 amount, uint256 total); - // event to ack unstaking which will start at deactivationEpoch - event UnstakeInit(address indexed user, uint256 indexed validatorId, uint256 deactivationEpoch, uint256 indexed amount); - - event SignerChange(uint256 indexed validatorId, address indexed oldSigner, address indexed newSigner); - event ReStaked(uint256 indexed validatorId, uint256 amount, uint256 total); - event Jailed(uint256 indexed validatorId, uint256 indexed exitEpoch); - event ThresholdChange(uint256 newThreshold, uint256 oldThreshold); - event DynastyValueChange(uint256 newDynasty, uint256 oldDynasty); - event RewardUpdate(uint256 newReward, uint256 oldReward); - event StakeUpdate(uint256 indexed validatorId, uint256 indexed newAmount); - event ClaimRewards(uint256 indexed validatorId, uint256 indexed amount, uint256 indexed totalAmount); - event StartAuction(uint256 indexed validatorId, uint256 indexed amount, uint256 indexed auctionAmount); - event ConfirmAuction(uint256 indexed newValidatorId, uint256 indexed oldValidatorId, uint256 indexed amount); - event TopUpFee(uint256 indexed validatorId, uint256 indexed fee); - event ClaimFee(uint256 indexed validatorId, uint256 indexed fee); - // Delegator events - event ShareMinted(uint256 indexed validatorId, address indexed user, uint256 indexed amount, uint256 tokens); - event ShareBurned(uint256 indexed validatorId, address indexed user, uint256 indexed amount, uint256 tokens); - event DelClaimRewards(uint256 indexed validatorId, address indexed user, uint256 indexed rewards, uint256 tokens); - event UpdateCommissionRate(uint256 indexed validatorId, uint256 indexed newCommissionRate, uint256 indexed oldCommissionRate); - - Registry public registry; - - modifier onlyValidatorContract(uint256 validatorId) { - address _contract; - (,,,,,,_contract,) = IStakeManager(registry.getStakeManagerAddress()).validators(validatorId); - require(_contract == msg.sender); - _; - } - - modifier StakeManagerOrValidatorContract(uint256 validatorId) { - address _contract; - address _stakeManager = registry.getStakeManagerAddress(); - (,,,,,,_contract,) = IStakeManager(_stakeManager).validators(validatorId); - require(_contract == msg.sender || _stakeManager == msg.sender); - _; - } - - modifier onlyStakeManager() { - require(registry.getStakeManagerAddress() == msg.sender); - _; - } - - constructor (address _registry) public { - registry = Registry(_registry); - } - - function logStaked(address signer, uint256 validatorId, uint256 activationEpoch, uint256 amount, uint256 total) public onlyStakeManager { - emit Staked( signer, validatorId, activationEpoch, amount, total); - } - - function logUnstaked(address user, uint256 validatorId, uint256 amount, uint256 total) public onlyStakeManager { - emit Unstaked(user, validatorId, amount, total); - } - - function logUnstakeInit(address user, uint256 validatorId, uint256 deactivationEpoch, uint256 amount) public onlyStakeManager { - emit UnstakeInit(user, validatorId, deactivationEpoch, amount); - } - - function logSignerChange(uint256 validatorId, address oldSigner, address newSigner) public onlyStakeManager { - emit SignerChange(validatorId, oldSigner, newSigner); - } - - function logReStaked(uint256 validatorId, uint256 amount, uint256 total) public onlyStakeManager { - emit ReStaked(validatorId, amount, total); - } - - function logJailed(uint256 validatorId, uint256 exitEpoch) public onlyStakeManager { - emit Jailed(validatorId, exitEpoch); - } - - function logThresholdChange(uint256 newThreshold, uint256 oldThreshold) public onlyStakeManager { - emit ThresholdChange(newThreshold, oldThreshold); - } - - function logDynastyValueChange(uint256 newDynasty, uint256 oldDynasty) public onlyStakeManager { - emit DynastyValueChange(newDynasty, oldDynasty); - } - - function logRewardUpdate(uint256 newReward, uint256 oldReward) public onlyStakeManager { - emit RewardUpdate(newReward, oldReward); - } - - function logStakeUpdate(uint256 validatorId) public StakeManagerOrValidatorContract(validatorId) { - emit StakeUpdate(validatorId, totalValidatorStake(validatorId)); - } - - function logClaimRewards(uint256 validatorId, uint256 amount, uint256 totalAmount) public onlyStakeManager { - emit ClaimRewards(validatorId, amount, totalAmount); - } - - function logStartAuction(uint256 validatorId, uint256 amount, uint256 auctionAmount) public onlyStakeManager { - emit StartAuction(validatorId, amount, auctionAmount); - } - - function logConfirmAuction(uint256 newValidatorId, uint256 oldValidatorId, uint256 amount) public onlyStakeManager { - emit ConfirmAuction(newValidatorId, oldValidatorId, amount); - } - - function logTopUpFee(uint256 validatorId, uint256 fee) public onlyStakeManager { - emit TopUpFee(validatorId, fee); - } - - function logClaimFee(uint256 validatorId, uint256 fee) public onlyStakeManager { - emit ClaimFee(validatorId, fee); - } - - function getStakerDetails(uint256 validatorId) public view returns(uint256 amount, uint256 activationEpoch, uint256 deactivationEpoch, address signer, uint256 _status) { - IStakeManager stakeManager = IStakeManager(registry.getStakeManagerAddress()); - IStakeManager.Status status; - (amount, , activationEpoch, deactivationEpoch, , signer, , status) = stakeManager.validators(validatorId); - _status = uint256(status); - } - - function totalValidatorStake(uint256 validatorId) public view returns (uint256 validatorStake) { - address contractAddress; - (validatorStake, , , , , , contractAddress, ) = IStakeManager(registry.getStakeManagerAddress()).validators(validatorId); - validatorStake += IStakeManager(contractAddress).activeAmount(); - } - - function getAccountStateRoot() public view returns(bytes32 accountStateRoot) { - accountStateRoot = IStakeManager(registry.getStakeManagerAddress()).accountStateRoot(); - } - - function getValidatorContractAddress(uint256 validatorId) public view returns(address ValidatorContract) { - (,,,,,,ValidatorContract,) = IStakeManager(registry.getStakeManagerAddress()).validators(validatorId); - } - - // validator Share contract logging func - function logShareMinted(uint256 validatorId, address user, uint256 amount, uint256 tokens) public onlyValidatorContract(validatorId) { - emit ShareMinted(validatorId, user, amount, tokens); - } - - function logShareBurned(uint256 validatorId, address user, uint256 amount, uint256 tokens) public onlyValidatorContract(validatorId) { - emit ShareBurned(validatorId, user, amount, tokens); - } - - function logDelClaimRewards(uint256 validatorId, address user, uint256 rewards, uint256 tokens) public onlyValidatorContract(validatorId) { - emit DelClaimRewards(validatorId, user, rewards, tokens); - } - - function logUpdateCommissionRate(uint256 validatorId, uint256 newCommissionRate, uint256 oldCommissionRate) public onlyValidatorContract(validatorId) { - emit UpdateCommissionRate(validatorId, newCommissionRate, oldCommissionRate); - } + event Staked( + address indexed signer, + uint256 indexed validatorId, + uint256 indexed activationEpoch, + uint256 amount, + uint256 total + ); + event Unstaked( + address indexed user, + uint256 indexed validatorId, + uint256 amount, + uint256 total + ); + // event to ack unstaking which will start at deactivationEpoch + event UnstakeInit( + address indexed user, + uint256 indexed validatorId, + uint256 deactivationEpoch, + uint256 indexed amount + ); + + event SignerChange( + uint256 indexed validatorId, + address indexed oldSigner, + address indexed newSigner + ); + event ReStaked(uint256 indexed validatorId, uint256 amount, uint256 total); + event Jailed(uint256 indexed validatorId, uint256 indexed exitEpoch); + event ThresholdChange(uint256 newThreshold, uint256 oldThreshold); + event DynastyValueChange(uint256 newDynasty, uint256 oldDynasty); + event RewardUpdate(uint256 newReward, uint256 oldReward); + event StakeUpdate(uint256 indexed validatorId, uint256 indexed newAmount); + event ClaimRewards( + uint256 indexed validatorId, + uint256 indexed amount, + uint256 indexed totalAmount + ); + event StartAuction( + uint256 indexed validatorId, + uint256 indexed amount, + uint256 indexed auctionAmount + ); + event ConfirmAuction( + uint256 indexed newValidatorId, + uint256 indexed oldValidatorId, + uint256 indexed amount + ); + event TopUpFee(uint256 indexed validatorId, uint256 indexed fee); + event ClaimFee(uint256 indexed validatorId, uint256 indexed fee); + // Delegator events + event ShareMinted( + uint256 indexed validatorId, + address indexed user, + uint256 indexed amount, + uint256 tokens + ); + event ShareBurned( + uint256 indexed validatorId, + address indexed user, + uint256 indexed amount, + uint256 tokens + ); + event DelClaimRewards( + uint256 indexed validatorId, + address indexed user, + uint256 indexed rewards, + uint256 tokens + ); + event UpdateCommissionRate( + uint256 indexed validatorId, + uint256 indexed newCommissionRate, + uint256 indexed oldCommissionRate + ); + + Registry public registry; + + modifier onlyValidatorContract(uint256 validatorId) { + address _contract; + (, , , , , , _contract, ) = IStakeManager( + registry.getStakeManagerAddress() + ) + .validators(validatorId); + require(_contract == msg.sender); + _; + } + + modifier StakeManagerOrValidatorContract(uint256 validatorId) { + address _contract; + address _stakeManager = registry.getStakeManagerAddress(); + (, , , , , , _contract, ) = IStakeManager(_stakeManager).validators( + validatorId + ); + require(_contract == msg.sender || _stakeManager == msg.sender); + _; + } + + modifier onlyStakeManager() { + require(registry.getStakeManagerAddress() == msg.sender); + _; + } + + constructor(address _registry) public { + registry = Registry(_registry); + } + + function logStaked( + address signer, + uint256 validatorId, + uint256 activationEpoch, + uint256 amount, + uint256 total + ) public onlyStakeManager { + emit Staked(signer, validatorId, activationEpoch, amount, total); + } + + function logUnstaked( + address user, + uint256 validatorId, + uint256 amount, + uint256 total + ) public onlyStakeManager { + emit Unstaked(user, validatorId, amount, total); + } + + function logUnstakeInit( + address user, + uint256 validatorId, + uint256 deactivationEpoch, + uint256 amount + ) public onlyStakeManager { + emit UnstakeInit(user, validatorId, deactivationEpoch, amount); + } + + function logSignerChange( + uint256 validatorId, + address oldSigner, + address newSigner + ) public onlyStakeManager { + emit SignerChange(validatorId, oldSigner, newSigner); + } + + function logReStaked(uint256 validatorId, uint256 amount, uint256 total) + public + onlyStakeManager + { + emit ReStaked(validatorId, amount, total); + } + + function logJailed(uint256 validatorId, uint256 exitEpoch) + public + onlyStakeManager + { + emit Jailed(validatorId, exitEpoch); + } + + function logThresholdChange(uint256 newThreshold, uint256 oldThreshold) + public + onlyStakeManager + { + emit ThresholdChange(newThreshold, oldThreshold); + } + + function logDynastyValueChange(uint256 newDynasty, uint256 oldDynasty) + public + onlyStakeManager + { + emit DynastyValueChange(newDynasty, oldDynasty); + } + + function logRewardUpdate(uint256 newReward, uint256 oldReward) + public + onlyStakeManager + { + emit RewardUpdate(newReward, oldReward); + } + + function logStakeUpdate(uint256 validatorId) + public + StakeManagerOrValidatorContract(validatorId) + { + emit StakeUpdate(validatorId, totalValidatorStake(validatorId)); + } + + function logClaimRewards( + uint256 validatorId, + uint256 amount, + uint256 totalAmount + ) public onlyStakeManager { + emit ClaimRewards(validatorId, amount, totalAmount); + } + + function logStartAuction( + uint256 validatorId, + uint256 amount, + uint256 auctionAmount + ) public onlyStakeManager { + emit StartAuction(validatorId, amount, auctionAmount); + } + + function logConfirmAuction( + uint256 newValidatorId, + uint256 oldValidatorId, + uint256 amount + ) public onlyStakeManager { + emit ConfirmAuction(newValidatorId, oldValidatorId, amount); + } + + function logTopUpFee(uint256 validatorId, uint256 fee) + public + onlyStakeManager + { + emit TopUpFee(validatorId, fee); + } + + function logClaimFee(uint256 validatorId, uint256 fee) + public + onlyStakeManager + { + emit ClaimFee(validatorId, fee); + } + + function getStakerDetails(uint256 validatorId) + public + view + returns ( + uint256 amount, + uint256 activationEpoch, + uint256 deactivationEpoch, + address signer, + uint256 _status + ) + { + IStakeManager stakeManager = IStakeManager( + registry.getStakeManagerAddress() + ); + IStakeManager.Status status; + ( + amount, + , + activationEpoch, + deactivationEpoch, + , + signer, + , + status + ) = stakeManager.validators(validatorId); + _status = uint256(status); + } + + function totalValidatorStake(uint256 validatorId) + public + view + returns (uint256 validatorStake) + { + address contractAddress; + (validatorStake, , , , , , contractAddress, ) = IStakeManager( + registry.getStakeManagerAddress() + ) + .validators(validatorId); + validatorStake += IStakeManager(contractAddress).activeAmount(); + } + + function getAccountStateRoot() + public + view + returns (bytes32 accountStateRoot) + { + accountStateRoot = IStakeManager(registry.getStakeManagerAddress()) + .accountStateRoot(); + } + + function getValidatorContractAddress(uint256 validatorId) + public + view + returns (address ValidatorContract) + { + (, , , , , , ValidatorContract, ) = IStakeManager( + registry.getStakeManagerAddress() + ) + .validators(validatorId); + } + + // validator Share contract logging func + function logShareMinted( + uint256 validatorId, + address user, + uint256 amount, + uint256 tokens + ) public onlyValidatorContract(validatorId) { + emit ShareMinted(validatorId, user, amount, tokens); + } + + function logShareBurned( + uint256 validatorId, + address user, + uint256 amount, + uint256 tokens + ) public onlyValidatorContract(validatorId) { + emit ShareBurned(validatorId, user, amount, tokens); + } + + function logDelClaimRewards( + uint256 validatorId, + address user, + uint256 rewards, + uint256 tokens + ) public onlyValidatorContract(validatorId) { + emit DelClaimRewards(validatorId, user, rewards, tokens); + } + + function logUpdateCommissionRate( + uint256 validatorId, + uint256 newCommissionRate, + uint256 oldCommissionRate + ) public onlyValidatorContract(validatorId) { + emit UpdateCommissionRate( + validatorId, + newCommissionRate, + oldCommissionRate + ); + } } diff --git a/contracts/staking/ValidatorShare.sol b/contracts/staking/ValidatorShare.sol index d9688d647..28cf0d3fc 100644 --- a/contracts/staking/ValidatorShare.sol +++ b/contracts/staking/ValidatorShare.sol @@ -1,28 +1,39 @@ pragma solidity ^0.5.2; -import { Registry } from "../common/Registry.sol"; +import {Registry} from "../common/Registry.sol"; -import { IValidatorShare } from "./IValidatorShare.sol"; +import {IValidatorShare} from "./IValidatorShare.sol"; // TODO: refactor each function to reusable smaller internal functions contract ValidatorShare is IValidatorShare { - // TODO: totalStake and active ammount issue - // TODO: transfer all the funds and rewards to/from stakeManager - constructor ( - uint256 _validatorId, - address _tokenAddress, - address _stakingLogger, - address _stakeManager) public - IValidatorShare(_validatorId, _tokenAddress, _stakingLogger, _stakeManager) { - } - - modifier onlyValidator() { - require(stakeManager.ownerOf(validatorId) == msg.sender); - _; - } - - function udpateRewards(uint256 _reward, uint256 _totalStake) external onlyOwner returns(uint256) { - /** + // TODO: totalStake and active ammount issue + // TODO: transfer all the funds and rewards to/from stakeManager + constructor( + uint256 _validatorId, + address _tokenAddress, + address _stakingLogger, + address _stakeManager + ) + public + IValidatorShare( + _validatorId, + _tokenAddress, + _stakingLogger, + _stakeManager + ) + {} + + modifier onlyValidator() { + require(stakeManager.ownerOf(validatorId) == msg.sender); + _; + } + + function udpateRewards(uint256 _reward, uint256 _totalStake) + external + onlyOwner + returns (uint256) + { + /** TODO: check for no revert on 0 commission and reduce logic for calculations TODO: better to add validator as one of share holder and restaking is simply buying more shares of pool @@ -34,124 +45,170 @@ contract ValidatorShare is IValidatorShare { - add rewards to pool rewards - returns total active stake for validator */ - uint256 stakePower; - uint256 valStake; - (valStake, , , , , , , ) = stakeManager.validators(validatorId);// to avoid Stack too deep :cry - stakePower = valStake.add(activeAmount);// validator + delegation stake power - uint256 _rewards = stakePower.mul(_reward).div(_totalStake); - - uint256 _valRewards = valStake.mul(_rewards).div(stakePower); - // add validator commission from delegation rewards - _valRewards = _valRewards.add(_rewards.sub(_valRewards).mul(commissionRate).div(100)); - _rewards = _rewards.sub(_valRewards); - validatorRewards = validatorRewards.add(_valRewards); - rewards = rewards.add(_rewards); - return stakePower; - } - - function updateCommissionRate(uint256 newCommissionRate) external onlyValidator { - //todo: constrains on updates, coolDown period - require(commissionRate <= 100, "Commission rate should be in range of 0-100"); - stakingLogger.logUpdateCommissionRate(validatorId, newCommissionRate, commissionRate); - commissionRate = newCommissionRate; - } - - function withdrawRewardsValidator() external onlyOwner returns(uint256 _rewards) { - _rewards = validatorRewards; - validatorRewards = 0; - } - - function exchangeRate() public view returns(uint256) { - return totalSupply() == 0 ? 100 : activeAmount.add(rewards).mul(100).div(totalSupply()); - } - - function buyVoucher(uint256 _amount) public onlyWhenUnlocked { - uint256 share = _amount.mul(100).div(exchangeRate()); - totalStake = totalStake.add(_amount); - amountStaked[msg.sender] = amountStaked[msg.sender].add(_amount); - require(token.transferFrom(msg.sender, address(this), _amount), "Transfer amount failed"); - _mint(msg.sender, share); - activeAmount = activeAmount.add(_amount); - stakeManager.updateValidatorState(validatorId, int256(_amount)); - - stakingLogger.logShareMinted(validatorId, msg.sender, _amount, share); - stakingLogger.logStakeUpdate(validatorId); - } - - function sellVoucher() public { - uint256 share = balanceOf(msg.sender); - require(share > 0, "Zero balance"); - uint256 _amount = exchangeRate().mul(share).div(100); - _burn(msg.sender, share); - stakeManager.updateValidatorState(validatorId, -int256(_amount)); - - activeAmount = activeAmount.sub(_amount); - if (_amount > amountStaked[msg.sender]) { - uint256 _rewards = _amount.sub(amountStaked[msg.sender]); - //withdrawTransfer - stakeManager.delegationTransfer(validatorId, _rewards, msg.sender); - _amount = _amount.sub(_rewards); + uint256 stakePower; + uint256 valStake; + (valStake, , , , , , , ) = stakeManager.validators(validatorId); // to avoid Stack too deep :cry + stakePower = valStake.add(activeAmount); // validator + delegation stake power + uint256 _rewards = stakePower.mul(_reward).div(_totalStake); + + uint256 _valRewards = valStake.mul(_rewards).div(stakePower); + // add validator commission from delegation rewards + _valRewards = _valRewards.add( + _rewards.sub(_valRewards).mul(commissionRate).div(100) + ); + _rewards = _rewards.sub(_valRewards); + validatorRewards = validatorRewards.add(_valRewards); + rewards = rewards.add(_rewards); + return stakePower; + } + + function updateCommissionRate(uint256 newCommissionRate) + external + onlyValidator + { + //todo: constrains on updates, coolDown period + require( + commissionRate <= 100, + "Commission rate should be in range of 0-100" + ); + stakingLogger.logUpdateCommissionRate( + validatorId, + newCommissionRate, + commissionRate + ); + commissionRate = newCommissionRate; + } + + function withdrawRewardsValidator() + external + onlyOwner + returns (uint256 _rewards) + { + _rewards = validatorRewards; + validatorRewards = 0; + } + + function exchangeRate() public view returns (uint256) { + return + totalSupply() == 0 + ? 100 + : activeAmount.add(rewards).mul(100).div(totalSupply()); + } + + function buyVoucher(uint256 _amount) public onlyWhenUnlocked { + uint256 share = _amount.mul(100).div(exchangeRate()); + totalStake = totalStake.add(_amount); + amountStaked[msg.sender] = amountStaked[msg.sender].add(_amount); + require( + token.transferFrom(msg.sender, address(this), _amount), + "Transfer amount failed" + ); + _mint(msg.sender, share); + activeAmount = activeAmount.add(_amount); + stakeManager.updateValidatorState(validatorId, int256(_amount)); + + stakingLogger.logShareMinted(validatorId, msg.sender, _amount, share); + stakingLogger.logStakeUpdate(validatorId); + } + + function sellVoucher() public { + uint256 share = balanceOf(msg.sender); + require(share > 0, "Zero balance"); + uint256 _amount = exchangeRate().mul(share).div(100); + _burn(msg.sender, share); + stakeManager.updateValidatorState(validatorId, -int256(_amount)); + + activeAmount = activeAmount.sub(_amount); + if (_amount > amountStaked[msg.sender]) { + uint256 _rewards = _amount.sub(amountStaked[msg.sender]); + //withdrawTransfer + stakeManager.delegationTransfer(validatorId, _rewards, msg.sender); + _amount = _amount.sub(_rewards); + } + + amountStaked[msg.sender] = 0; // TODO: add partial sell amountStaked[msg.sender].sub(_amount); + delegators[msg.sender] = Delegator({ + amount: _amount, + withdrawEpoch: stakeManager.currentEpoch().add( + stakeManager.WITHDRAWAL_DELAY() + ) + }); + + stakingLogger.logShareBurned(validatorId, msg.sender, _amount, share); + stakingLogger.logStakeUpdate(validatorId); + } + + function withdrawRewards() public { + uint256 liquidRewards = getLiquidRewards(msg.sender); + uint256 sharesToBurn = liquidRewards.mul(100).div(exchangeRate()); + // if (sharesToBurn > 0) + _burn(msg.sender, sharesToBurn); + rewards = rewards.sub(liquidRewards); + stakeManager.delegationTransfer(validatorId, liquidRewards, msg.sender); + stakingLogger.logDelClaimRewards( + validatorId, + msg.sender, + liquidRewards, + sharesToBurn + ); } - amountStaked[msg.sender] = 0; // TODO: add partial sell amountStaked[msg.sender].sub(_amount); - delegators[msg.sender] = Delegator({ - amount: _amount, - withdrawEpoch: stakeManager.currentEpoch().add(stakeManager.WITHDRAWAL_DELAY()) - }); - - stakingLogger.logShareBurned(validatorId, msg.sender, _amount, share); - stakingLogger.logStakeUpdate(validatorId); - } - - function withdrawRewards() public { - uint256 liquidRewards = getLiquidRewards(msg.sender); - uint256 sharesToBurn = liquidRewards.mul(100).div(exchangeRate()); - // if (sharesToBurn > 0) - _burn(msg.sender, sharesToBurn); - rewards = rewards.sub(liquidRewards); - stakeManager.delegationTransfer(validatorId, liquidRewards, msg.sender); - stakingLogger.logDelClaimRewards(validatorId, msg.sender, liquidRewards, sharesToBurn); - } - - function reStake() public { - /** + function reStake() public { + /** - only active amount is considers as active stake - move reward amount to active stake pool - no shares are minted */ - uint256 liquidRewards = getLiquidRewards(msg.sender); - amountStaked[msg.sender] = amountStaked[msg.sender].add(liquidRewards); - totalStake = totalStake.add(liquidRewards); - activeAmount = activeAmount.add(liquidRewards); - stakeManager.delegationTransfer(validatorId, liquidRewards, address(this)); - stakeManager.updateValidatorState(validatorId, int256(liquidRewards)); - rewards = rewards.sub(liquidRewards); - stakingLogger.logStakeUpdate(validatorId); - // TODO: add restake event - } - - function getLiquidRewards(address user) public view returns(uint256 liquidRewards) { - uint256 share = balanceOf(user); - uint256 _exchangeRate = exchangeRate(); - require(share > 0, "Zero balance"); - uint256 totalTokens = _exchangeRate.mul(share).div(100); - liquidRewards = totalTokens.sub(amountStaked[user]); - } - - function unStakeClaimTokens() public { - Delegator storage delegator = delegators[msg.sender]; - totalStake = totalStake.sub(delegator.amount); - require(delegator.withdrawEpoch <= stakeManager.currentEpoch() && delegator.amount > 0, "Incomplete withdrawal period"); - require(token.transfer(msg.sender, delegator.amount), "Transfer amount failed"); - delete delegators[msg.sender]; - } - - function slash(uint256 slashRate, uint256 startEpoch, uint256 endEpoch) public {} - // function _slashActive() internal {} - // function _slashInActive() internal {} - - function _transfer(address from, address to, uint256 value) internal { - revert("Disabled"); - } + uint256 liquidRewards = getLiquidRewards(msg.sender); + amountStaked[msg.sender] = amountStaked[msg.sender].add(liquidRewards); + totalStake = totalStake.add(liquidRewards); + activeAmount = activeAmount.add(liquidRewards); + stakeManager.delegationTransfer( + validatorId, + liquidRewards, + address(this) + ); + stakeManager.updateValidatorState(validatorId, int256(liquidRewards)); + rewards = rewards.sub(liquidRewards); + stakingLogger.logStakeUpdate(validatorId); + // TODO: add restake event + } + + function getLiquidRewards(address user) + public + view + returns (uint256 liquidRewards) + { + uint256 share = balanceOf(user); + uint256 _exchangeRate = exchangeRate(); + require(share > 0, "Zero balance"); + uint256 totalTokens = _exchangeRate.mul(share).div(100); + liquidRewards = totalTokens.sub(amountStaked[user]); + } + + function unStakeClaimTokens() public { + Delegator storage delegator = delegators[msg.sender]; + totalStake = totalStake.sub(delegator.amount); + require( + delegator.withdrawEpoch <= stakeManager.currentEpoch() && + delegator.amount > 0, + "Incomplete withdrawal period" + ); + require( + token.transfer(msg.sender, delegator.amount), + "Transfer amount failed" + ); + delete delegators[msg.sender]; + } + + function slash(uint256 slashRate, uint256 startEpoch, uint256 endEpoch) + public + {} + // function _slashActive() internal {} + // function _slashInActive() internal {} + + function _transfer(address from, address to, uint256 value) internal { + revert("Disabled"); + } } diff --git a/contracts/test/MarketplacePredicateTest.sol b/contracts/test/MarketplacePredicateTest.sol index 4d896f19d..770094b24 100644 --- a/contracts/test/MarketplacePredicateTest.sol +++ b/contracts/test/MarketplacePredicateTest.sol @@ -1,53 +1,73 @@ pragma solidity ^0.5.2; -import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol"; - -import { MarketplacePredicate } from "../root/predicates/MarketplacePredicate.sol"; -import { ERC20Predicate } from "../root/predicates/ERC20Predicate.sol"; +import {RLPReader} from "solidity-rlp/contracts/RLPReader.sol"; +import { + MarketplacePredicate +} from "../root/predicates/MarketplacePredicate.sol"; +import {ERC20Predicate} from "../root/predicates/ERC20Predicate.sol"; contract MarketplacePredicateTest is MarketplacePredicate { + constructor() + public + MarketplacePredicate(address(0x0), address(0x0), address(0x0)) + {} - constructor() - MarketplacePredicate(address(0x0), address(0x0), address(0x0)) - public {} - - function processLogTransferReceiptTest( - address predicate, - bytes memory data, - address participant) - public - view - returns(bytes memory b) - { - ReferenceTxData memory _referenceTx = super.processLogTransferReceipt(predicate, data, participant, false, false); - b = abi.encode(_referenceTx.closingBalance, _referenceTx.age, _referenceTx.childToken, _referenceTx.rootToken); - } + function processLogTransferReceiptTest( + address predicate, + bytes memory data, + address participant + ) public view returns (bytes memory b) { + ReferenceTxData memory _referenceTx = super.processLogTransferReceipt( + predicate, + data, + participant, + false, + false + ); + b = abi.encode( + _referenceTx.closingBalance, + _referenceTx.age, + _referenceTx.childToken, + _referenceTx.rootToken + ); + } - function processExitTx(bytes memory exitTx) - public - view - returns(bytes memory b) - { - ExitTxData memory txData = super.processExitTx(exitTx, msg.sender); - b = abi.encode(txData.amount1, txData.amount2, txData.token1, txData.token2, txData.counterParty); - } + function processExitTx(bytes memory exitTx) + public + view + returns (bytes memory b) + { + ExitTxData memory txData = super.processExitTx(exitTx, msg.sender); + b = abi.encode( + txData.amount1, + txData.amount2, + txData.token1, + txData.token2, + txData.counterParty + ); + } - function testGetAddressFromTx(bytes memory exitTx) - public - pure - returns (address signer, bytes32 txHash) - { - RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); - (signer, txHash) = getAddressFromTx(txList); - } + function testGetAddressFromTx(bytes memory exitTx) + public + pure + returns (address signer, bytes32 txHash) + { + RLPReader.RLPItem[] memory txList = exitTx.toRlpItem().toList(); + (signer, txHash) = getAddressFromTx(txList); + } - function decodeExitTx(bytes memory exitTx) - internal - pure - returns(ExitTxData memory txData) - { - (txData.amount1, txData.amount2, txData.token1, txData.token2, txData.counterParty) = abi.decode( - exitTx, (uint256, uint256, address, address, address)); - } + function decodeExitTx(bytes memory exitTx) + internal + pure + returns (ExitTxData memory txData) + { + ( + txData.amount1, + txData.amount2, + txData.token1, + txData.token2, + txData.counterParty + ) = abi.decode(exitTx, (uint256, uint256, address, address, address)); + } } diff --git a/contracts/test/StakeManagerTest.sol b/contracts/test/StakeManagerTest.sol index fcf2bde23..2af39fcde 100644 --- a/contracts/test/StakeManagerTest.sol +++ b/contracts/test/StakeManagerTest.sol @@ -1,18 +1,35 @@ pragma solidity ^0.5.2; -import { StakeManager } from "../staking/StakeManager.sol"; - +import {StakeManager} from "../staking/StakeManager.sol"; contract StakeManagerTest is StakeManager { - modifier onlyRootChain() { - _; - } + modifier onlyRootChain() { + _; + } - constructor (address _registry, address _rootChain, address _stakingLogger, address _validatorShareFactory) StakeManager(_registry, _rootChain, _stakingLogger, _validatorShareFactory) public { - checkPointBlockInterval = 1; - } + constructor( + address _registry, + address _rootChain, + address _stakingLogger, + address _validatorShareFactory + ) + public + StakeManager( + _registry, + _rootChain, + _stakingLogger, + _validatorShareFactory + ) + { + checkPointBlockInterval = 1; + } - function checkSignatures(uint256 blockInterval, bytes32 voteHash, bytes32 stateRoot, bytes memory sigs) public onlyRootChain returns(uint256) { - return CHECKPOINT_REWARD; // for dummy tests return full reward - } + function checkSignatures( + uint256 blockInterval, + bytes32 voteHash, + bytes32 stateRoot, + bytes memory sigs + ) public onlyRootChain returns (uint256) { + return CHECKPOINT_REWARD; // for dummy tests return full reward + } } diff --git a/contracts/test/TestMaticChildERC20.sol b/contracts/test/TestMaticChildERC20.sol index b529a4b19..e7c0d596c 100644 --- a/contracts/test/TestMaticChildERC20.sol +++ b/contracts/test/TestMaticChildERC20.sol @@ -1,8 +1,7 @@ pragma solidity ^0.5.11; -import { MaticChildERC20 } from "../child/MaticChildERC20.sol"; - +import {MaticChildERC20} from "../child/MaticChildERC20.sol"; contract TestMaticChildERC20 is MaticChildERC20 { - function() external payable {} + function() external payable {} }