Created
February 12, 2021 09:11
-
-
Save jdkanani/b50a818374d3e96f7a9aaf9b96c3780b to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.5.9+commit.c68bc34e.js&optimize=false&runs=200&gist=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.4.22 <0.7.0; | |
/** | |
* @title Storage | |
* @dev Store & retrieve value in a variable | |
*/ | |
contract Storage { | |
uint256 number; | |
/** | |
* @dev Store value in variable | |
* @param num value to store | |
*/ | |
function store(uint256 num) public { | |
number = num; | |
} | |
/** | |
* @dev Return value | |
* @return value of 'number' | |
*/ | |
function retrieve() public view returns (uint256){ | |
return number; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.4.22 <0.7.0; | |
/** | |
* @title Owner | |
* @dev Set & change owner | |
*/ | |
contract Owner { | |
address private owner; | |
// event for EVM logging | |
event OwnerSet(address indexed oldOwner, address indexed newOwner); | |
// modifier to check if caller is owner | |
modifier isOwner() { | |
// If the first argument of 'require' evaluates to 'false', execution terminates and all | |
// changes to the state and to Ether balances are reverted. | |
// This used to consume all gas in old EVM versions, but not anymore. | |
// It is often a good idea to use 'require' to check if functions are called correctly. | |
// As a second argument, you can also provide an explanation about what went wrong. | |
require(msg.sender == owner, "Caller is not owner"); | |
_; | |
} | |
/** | |
* @dev Set contract deployer as owner | |
*/ | |
constructor() public { | |
owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor | |
emit OwnerSet(address(0), owner); | |
} | |
/** | |
* @dev Change owner | |
* @param newOwner address of new owner | |
*/ | |
function changeOwner(address newOwner) public isOwner { | |
emit OwnerSet(owner, newOwner); | |
owner = newOwner; | |
} | |
/** | |
* @dev Return owner address | |
* @return address of owner | |
*/ | |
function getOwner() external view returns (address) { | |
return owner; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity 0.7.0; | |
// IStateReceiver represents interface to receive state | |
interface IStateReceiver { | |
function onStateReceive(uint256 stateId, bytes calldata data) external; | |
} | |
// IFxMessageProcessor represents interface to process message | |
interface IFxMessageProcessor { | |
function onMessageReceive(uint256 stateId, address rootMessageSender, bytes calldata data) external; | |
} | |
/** | |
* @title FxChild | |
*/ | |
contract FxChild is IStateReceiver { | |
event NewMessage(address rootMessageSender, address receiver, bytes data); | |
function onStateReceive(uint256, bytes calldata _data) external override { | |
// require(msg.sender == address(0x0000000000000000000000000000000000001000), "Invalid sender"); | |
(address rootMessageSender, address receiver, bytes memory data) = abi.decode(_data, (address, address, bytes)); | |
emit NewMessage(rootMessageSender, receiver, data); | |
// IFxMessageProcessor(receiver).onMessageReceive(stateId, rootMessageSender, data); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// File: contracts/common/lib/ECVerify.sol | |
pragma solidity ^0.5.17; | |
library ECVerify { | |
function ecrecovery(bytes32 hash, uint[3] memory sig) | |
internal | |
pure | |
returns (address) | |
{ | |
bytes32 r; | |
bytes32 s; | |
uint8 v; | |
assembly { | |
r := mload(sig) | |
s := mload(add(sig, 32)) | |
v := byte(31, mload(add(sig, 64))) | |
} | |
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { | |
return address(0x0); | |
} | |
// 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 | |
address result = ecrecover(hash, v, r, s); | |
// ecrecover returns zero on error | |
require(result != address(0x0)); | |
return result; | |
} | |
function ecrecovery(bytes32 hash, bytes memory sig) | |
internal | |
pure | |
returns (address) | |
{ | |
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 | |
address result = ecrecover(hash, v, r, s); | |
// ecrecover returns zero on error | |
require(result != address(0x0)); | |
return result; | |
} | |
function ecrecovery(bytes32 hash, uint8 v, bytes32 r, bytes32 s) | |
internal | |
pure | |
returns (address) | |
{ | |
// get address out of hash and signature | |
address result = ecrecover(hash, v, r, s); | |
// ecrecover returns zero on error | |
require(result != address(0x0), "signature verification failed"); | |
return result; | |
} | |
function ecverify(bytes32 hash, bytes memory sig, address signer) | |
internal | |
pure | |
returns (bool) | |
{ | |
return signer == ecrecovery(hash, sig); | |
} | |
} | |
contract CheckSignaturesTest { | |
function checkSignatures( | |
bytes calldata data, | |
uint256[3][] calldata sigs | |
) external pure returns (address[] memory signers) { | |
signers = new address[](sigs.length); | |
bytes32 voteHash = keccak256(abi.encodePacked(bytes(hex"01"), data)); | |
for (uint256 i = 0; i < sigs.length; ++i) { | |
address signer = ECVerify.ecrecovery(voteHash, sigs[i]); | |
signers[i] = signer; | |
} | |
return signers; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: UNLICENSED | |
// File contracts/lib/IERC20.sol | |
pragma solidity 0.7.3; | |
/** | |
* @dev Interface of the ERC20 standard as defined in the EIP. | |
*/ | |
interface IERC20 { | |
function totalSupply() external view returns (uint256); | |
function balanceOf(address account) external view returns (uint256); | |
function transfer(address recipient, uint256 amount) external returns (bool); | |
function allowance(address owner, address spender) external view returns (uint256); | |
function approve(address spender, uint256 amount) external returns (bool); | |
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); | |
event Transfer(address indexed from, address indexed to, uint256 value); | |
event Approval(address indexed owner, address indexed spender, uint256 value); | |
} | |
// File contracts/lib/Ownable.sol | |
pragma solidity 0.7.3; | |
abstract contract Ownable { | |
address private _owner; | |
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | |
constructor() { | |
address msgSender = msg.sender; | |
_owner = msgSender; | |
emit OwnershipTransferred(address(0), msgSender); | |
} | |
function owner() public view returns (address) { | |
return _owner; | |
} | |
modifier onlyOwner() { | |
require(_owner == msg.sender, "Ownable: caller is not the owner"); | |
_; | |
} | |
function transferOwnership(address newOwner) public virtual onlyOwner { | |
require(newOwner != address(0), "Ownable: new owner is the zero address"); | |
emit OwnershipTransferred(_owner, newOwner); | |
_owner = newOwner; | |
} | |
} | |
// File contracts/lib/DelegationProxy.sol | |
pragma solidity 0.7.3; | |
interface StakingNFT { | |
function balanceOf(address owner) external view returns (uint256 balance); | |
function ownerOf(uint256 tokenId) external view returns (address owner); | |
function approve(address to, uint256 tokenId) external; | |
function getApproved(uint256 tokenId) external view returns (address operator); | |
function setApprovalForAll(address operator, bool _approved) external; | |
function isApprovedForAll(address owner, address operator) external view returns (bool); | |
function transferFrom(address from, address to, uint256 tokenId) external; | |
function safeTransferFrom(address from, address to, uint256 tokenId) external; | |
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) external; | |
} | |
interface ValidatorShare { | |
function buyVoucher(uint256, uint256) external; | |
function withdrawRewards() external; | |
function sellVoucher(uint256, uint256) external; | |
function unstakeClaimTokens() external; | |
} | |
interface ValidatorShare_New { | |
function buyVoucher(uint256, uint256) external returns(uint256); | |
function withdrawRewards() external; | |
function sellVoucher(uint256, uint256) external; | |
function unstakeClaimTokens() external; | |
function sellVoucher_new(uint256, uint256) external; | |
function unstakeClaimTokens_new(uint256) external; | |
} | |
interface IStakeManager { | |
function getValidatorContract(uint256 validatorId) external view returns (address); | |
function token() external view returns (IERC20); | |
function NFTContract() external view returns (StakingNFT); | |
} | |
contract DelegationProxy is Ownable { | |
uint256[] public validatorsList; | |
mapping(uint256 => bool) public validatorsLookup; | |
IStakeManager public stakeManager; | |
constructor(IStakeManager _stakeManager) { | |
require(_stakeManager != IStakeManager(0x0)); | |
stakeManager = _stakeManager; | |
} | |
function withdrawTokens(address tokenAddress, uint256 amount) public onlyOwner { | |
IERC20(tokenAddress).transfer(owner(), amount); | |
} | |
function delegate(uint256[] memory validators, uint256[] memory amount, uint256 totalAmount) public onlyOwner { | |
require(validators.length == amount.length); | |
IERC20 token = stakeManager.token(); | |
token.approve(address(stakeManager), totalAmount); | |
for (uint256 i = 0; i < validators.length; ++i) { | |
uint256 validatorId = validators[i]; | |
if (!validatorsLookup[validatorId]) { | |
validatorsLookup[validatorId] = true; | |
validatorsList.push(validatorId); | |
} | |
ValidatorShare delegationContract = ValidatorShare(stakeManager.getValidatorContract(validatorId)); | |
require(delegationContract != ValidatorShare(0x0)); | |
// buy voucher | |
delegationContract.buyVoucher(amount[i], 0); | |
} | |
} | |
function delegate_new(uint256[] memory validators, uint256[] memory amount, uint256 totalAmount) public onlyOwner { | |
require(validators.length == amount.length); | |
IERC20 token = stakeManager.token(); | |
token.approve(address(stakeManager), totalAmount); | |
for (uint256 i = 0; i < validators.length; ++i) { | |
uint256 validatorId = validators[i]; | |
if (!validatorsLookup[validatorId]) { | |
validatorsLookup[validatorId] = true; | |
validatorsList.push(validatorId); | |
} | |
ValidatorShare_New delegationContract = ValidatorShare_New(stakeManager.getValidatorContract(validatorId)); | |
require(delegationContract != ValidatorShare_New(0x0)); | |
// buy voucher | |
delegationContract.buyVoucher(amount[i], 0); | |
} | |
} | |
function transferRewards(uint256[] memory validators) public onlyOwner { | |
IERC20 token = stakeManager.token(); | |
StakingNFT nft = stakeManager.NFTContract(); | |
uint256 tokenBalanceBefore = token.balanceOf(address(this)); | |
for (uint256 i = 0; i < validators.length; ++i) { | |
uint256 validatorId = validators[i]; | |
ValidatorShare delegationContract = ValidatorShare(stakeManager.getValidatorContract(validatorId)); | |
require(delegationContract != ValidatorShare(0x0)); | |
delegationContract.withdrawRewards(); | |
uint256 rewards = token.balanceOf(address(this)) - tokenBalanceBefore; | |
token.transfer(nft.ownerOf(validatorId), rewards); | |
} | |
} | |
function collectRewards(uint256[] memory validators) public onlyOwner { | |
for (uint256 i = 0; i < validators.length; ++i) { | |
uint256 validatorId = validators[i]; | |
ValidatorShare delegationContract = ValidatorShare(stakeManager.getValidatorContract(validatorId)); | |
require(delegationContract != ValidatorShare(0x0)); | |
delegationContract.withdrawRewards(); | |
} | |
} | |
function sellVoucher(uint256 validatorId, uint256 claimAmount, uint256 maximumSharesToBurn) public onlyOwner { | |
ValidatorShare delegationContract = ValidatorShare(stakeManager.getValidatorContract(validatorId)); | |
require(delegationContract != ValidatorShare(0x0)); | |
delegationContract.sellVoucher(claimAmount, maximumSharesToBurn); | |
} | |
function sellVoucher_new(uint256 validatorId, uint256 claimAmount, uint256 maximumSharesToBurn) public onlyOwner { | |
ValidatorShare_New delegationContract = ValidatorShare_New(stakeManager.getValidatorContract(validatorId)); | |
require(delegationContract != ValidatorShare_New(0x0)); | |
delegationContract.sellVoucher_new(claimAmount, maximumSharesToBurn); | |
} | |
function unstakeClaimTokens(uint256 validatorId) public onlyOwner { | |
ValidatorShare delegationContract = ValidatorShare(stakeManager.getValidatorContract(validatorId)); | |
require(delegationContract != ValidatorShare(0x0)); | |
delegationContract.unstakeClaimTokens(); | |
} | |
function unstakeClaimTokens_new(uint256 validatorId, uint256 unbondNonce) public onlyOwner { | |
ValidatorShare_New delegationContract = ValidatorShare_New(stakeManager.getValidatorContract(validatorId)); | |
require(delegationContract != ValidatorShare_New(0x0)); | |
delegationContract.unstakeClaimTokens_new(unbondNonce); | |
} | |
function callAny(address target, bytes memory data) public onlyOwner { | |
(bool success, ) = target.call(data); /* bytes memory returnData */ | |
require(success, "Call failed"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity 0.5.9; | |
contract DepositTest { | |
event Transfer( | |
address indexed from, | |
address indexed to, | |
uint256 amount | |
); | |
event Deposit( | |
address rootToken, | |
address user, | |
uint256 amountOrTokenId, | |
uint256 depositCount | |
); | |
struct D { | |
address rootToken; | |
address user; | |
uint256 amountOrTokenId; | |
uint256 depositCount; | |
} | |
mapping(uint256 => D) public ds; | |
constructor() public payable { | |
} | |
event LogTransfer( | |
address indexed token, | |
address indexed f, | |
address indexed to, | |
uint256 amount, | |
uint256 input1, | |
uint256 input2, | |
uint256 output1, | |
uint256 output2 | |
); | |
event LogFeeTransfer( | |
address indexed token, | |
address indexed f, | |
address indexed to, | |
uint256 amount, | |
uint256 input1, | |
uint256 input2, | |
uint256 output1, | |
uint256 output2 | |
); | |
function e() public payable { | |
emit Deposit(0x48aA8D4AF32551892FCF08Ad63Be7dD206D46F65, 0x48aA8D4AF32551892FCF08Ad63Be7dD206D46F65, 10, 20); | |
} | |
function transferTo(address payable to) public payable { | |
to.transfer(msg.value); | |
} | |
// function e1() public { | |
// deposit1(9, hex"1234", 0x48aA8D4AF32551892FCF08Ad63Be7dD206D46F65, 10, hex"1232"); | |
// } | |
// function c(bytes memory payload) public { | |
// address(this).call(payload); | |
// } | |
// function deposit(uint256 depositId, bytes memory uy, address to, uint256 amount, bytes memory ab) public { | |
// emit Deposit(depositId, uy, to, amount, ab); | |
// emit PackedDeposit(abi.encode(depositId, to, amount, ab)); | |
// } | |
function onStateReceive( | |
uint256 stateId, | |
bytes memory payload | |
) public { | |
require(msg.sender == address(0x0000000000000000000000000000000000001001)); | |
// address(this).call(abi.encodePacked(bytes4(keccak256("depositTokens(address,address,bytes,uint256,uint256)")), payload)); | |
address rootToken; | |
address user; | |
uint256 amountOrTokenId; | |
uint256 depositCount; | |
(rootToken, user, amountOrTokenId, depositCount) = abi.decode(payload, (address,address,uint256,uint256)); | |
depositTokens(rootToken,user, amountOrTokenId, depositCount); | |
} | |
function depositTokens( | |
address rootToken, | |
address user, | |
uint256 amountOrTokenId, | |
uint256 depositCount | |
) internal { | |
emit Deposit(rootToken, user, amountOrTokenId, depositCount); | |
emit Transfer(rootToken, user, amountOrTokenId); | |
ds[depositCount] = D({ | |
rootToken: rootToken, | |
user: user, | |
amountOrTokenId: amountOrTokenId, | |
depositCount: depositCount | |
}); | |
} | |
// counter | |
uint256 public counter = 0; | |
event StateSynced(uint256 indexed id, address indexed contractAddress, bytes data); | |
// sync state | |
function syncState(address receiver, bytes memory data) public /* onlyRegistered(receiver) */ { | |
counter = counter + 1; | |
emit StateSynced(counter, receiver, data); | |
} | |
function TransferTokens(address from, address to, uint256 amount) public { | |
emit Transfer(from, to, amount); | |
} | |
// function deposit1(uint256 depositId, bytes memory uy, address to, uint256 amount, bytes memory ab) public { | |
// emit Deposit(depositId, uy, to, amount, ab); | |
// emit PackedDeposit1(msg.data); | |
// emit PackedDeposit(abi.encode(depositId, uy, to, amount, ab)); | |
// } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity 0.7.3; | |
// IStateReceiver represents interface to receive state | |
interface IStateReceiver { | |
function onStateReceive(uint256 stateId, bytes calldata data) external; | |
} | |
// IFxMessageProcessor represents interface to process message | |
interface IFxMessageProcessor { | |
function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes calldata data) external; | |
} | |
/** | |
* @title FxChild child contract for state receiver | |
*/ | |
contract FxChild is IStateReceiver { | |
address public fxRoot; | |
event NewFxMessage(address rootMessageSender, address receiver, bytes data); | |
function setFxRoot(address _fxRoot) public { | |
require(fxRoot == address(0x0)); | |
fxRoot = _fxRoot; | |
} | |
function onStateReceive(uint256 stateId, bytes calldata _data) external override { | |
require(msg.sender == address(0x0000000000000000000000000000000000001001), "Invalid sender"); | |
(address rootMessageSender, address receiver, bytes memory data) = abi.decode(_data, (address, address, bytes)); | |
emit NewFxMessage(rootMessageSender, receiver, data); | |
IFxMessageProcessor(receiver).processMessageFromRoot(stateId, rootMessageSender, data); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity 0.7.3; | |
/** | |
* @dev Wrappers over Solidity's arithmetic operations with added overflow | |
* checks. | |
* | |
* Arithmetic operations in Solidity wrap on overflow. This can easily result | |
* in bugs, because programmers usually assume that an overflow raises an | |
* error, which is the standard behavior in high level programming languages. | |
* `SafeMath` restores this intuition by reverting the transaction when an | |
* operation overflows. | |
* | |
* Using this library instead of the unchecked operations eliminates an entire | |
* class of bugs, so it's recommended to use it always. | |
*/ | |
library SafeMath { | |
/** | |
* @dev Returns the addition of two unsigned integers, reverting on | |
* overflow. | |
* | |
* Counterpart to Solidity's `+` operator. | |
* | |
* Requirements: | |
* | |
* - Addition cannot overflow. | |
*/ | |
function add(uint256 a, uint256 b) internal pure returns (uint256) { | |
uint256 c = a + b; | |
require(c >= a, "SafeMath: addition overflow"); | |
return c; | |
} | |
/** | |
* @dev Returns the subtraction of two unsigned integers, reverting on | |
* overflow (when the result is negative). | |
* | |
* Counterpart to Solidity's `-` operator. | |
* | |
* Requirements: | |
* | |
* - Subtraction cannot overflow. | |
*/ | |
function sub(uint256 a, uint256 b) internal pure returns (uint256) { | |
return sub(a, b, "SafeMath: subtraction overflow"); | |
} | |
/** | |
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on | |
* overflow (when the result is negative). | |
* | |
* Counterpart to Solidity's `-` operator. | |
* | |
* Requirements: | |
* | |
* - Subtraction cannot overflow. | |
*/ | |
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { | |
require(b <= a, errorMessage); | |
uint256 c = a - b; | |
return c; | |
} | |
/** | |
* @dev Returns the multiplication of two unsigned integers, reverting on | |
* overflow. | |
* | |
* Counterpart to Solidity's `*` operator. | |
* | |
* Requirements: | |
* | |
* - Multiplication cannot overflow. | |
*/ | |
function mul(uint256 a, uint256 b) internal pure returns (uint256) { | |
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the | |
// benefit is lost if 'b' is also tested. | |
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 | |
if (a == 0) { | |
return 0; | |
} | |
uint256 c = a * b; | |
require(c / a == b, "SafeMath: multiplication overflow"); | |
return c; | |
} | |
/** | |
* @dev Returns the integer division of two unsigned integers. Reverts on | |
* division by zero. The result is rounded towards zero. | |
* | |
* Counterpart to Solidity's `/` operator. Note: this function uses a | |
* `revert` opcode (which leaves remaining gas untouched) while Solidity | |
* uses an invalid opcode to revert (consuming all remaining gas). | |
* | |
* Requirements: | |
* | |
* - The divisor cannot be zero. | |
*/ | |
function div(uint256 a, uint256 b) internal pure returns (uint256) { | |
return div(a, b, "SafeMath: division by zero"); | |
} | |
/** | |
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on | |
* division by zero. The result is rounded towards zero. | |
* | |
* Counterpart to Solidity's `/` operator. Note: this function uses a | |
* `revert` opcode (which leaves remaining gas untouched) while Solidity | |
* uses an invalid opcode to revert (consuming all remaining gas). | |
* | |
* Requirements: | |
* | |
* - The divisor cannot be zero. | |
*/ | |
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { | |
require(b > 0, errorMessage); | |
uint256 c = a / b; | |
// assert(a == b * c + a % b); // There is no case in which this doesn't hold | |
return c; | |
} | |
/** | |
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), | |
* Reverts when dividing by zero. | |
* | |
* Counterpart to Solidity's `%` operator. This function uses a `revert` | |
* opcode (which leaves remaining gas untouched) while Solidity uses an | |
* invalid opcode to revert (consuming all remaining gas). | |
* | |
* Requirements: | |
* | |
* - The divisor cannot be zero. | |
*/ | |
function mod(uint256 a, uint256 b) internal pure returns (uint256) { | |
return mod(a, b, "SafeMath: modulo by zero"); | |
} | |
/** | |
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), | |
* Reverts with custom message when dividing by zero. | |
* | |
* Counterpart to Solidity's `%` operator. This function uses a `revert` | |
* opcode (which leaves remaining gas untouched) while Solidity uses an | |
* invalid opcode to revert (consuming all remaining gas). | |
* | |
* Requirements: | |
* | |
* - The divisor cannot be zero. | |
*/ | |
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { | |
require(b != 0, errorMessage); | |
return a % b; | |
} | |
} | |
/** | |
* @dev Interface of the ERC20 standard as defined in the EIP. | |
*/ | |
interface IERC20 { | |
/** | |
* @dev Returns the amount of tokens in existence. | |
*/ | |
function totalSupply() external view returns (uint256); | |
/** | |
* @dev Returns the amount of tokens owned by `account`. | |
*/ | |
function balanceOf(address account) external view returns (uint256); | |
/** | |
* @dev Moves `amount` tokens from the caller's account to `recipient`. | |
* | |
* Returns a boolean value indicating whether the operation succeeded. | |
* | |
* Emits a {Transfer} event. | |
*/ | |
function transfer(address recipient, uint256 amount) external returns (bool); | |
/** | |
* @dev Returns the remaining number of tokens that `spender` will be | |
* allowed to spend on behalf of `owner` through {transferFrom}. This is | |
* zero by default. | |
* | |
* This value changes when {approve} or {transferFrom} are called. | |
*/ | |
function allowance(address owner, address spender) external view returns (uint256); | |
/** | |
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens. | |
* | |
* Returns a boolean value indicating whether the operation succeeded. | |
* | |
* IMPORTANT: Beware that changing an allowance with this method brings the risk | |
* that someone may use both the old and the new allowance by unfortunate | |
* transaction ordering. One possible solution to mitigate this race | |
* condition is to first reduce the spender's allowance to 0 and set the | |
* desired value afterwards: | |
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 | |
* | |
* Emits an {Approval} event. | |
*/ | |
function approve(address spender, uint256 amount) external returns (bool); | |
/** | |
* @dev Moves `amount` tokens from `sender` to `recipient` using the | |
* allowance mechanism. `amount` is then deducted from the caller's | |
* allowance. | |
* | |
* Returns a boolean value indicating whether the operation succeeded. | |
* | |
* Emits a {Transfer} event. | |
*/ | |
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); | |
/** | |
* @dev Emitted when `value` tokens are moved from one account (`from`) to | |
* another (`to`). | |
* | |
* Note that `value` may be zero. | |
*/ | |
event Transfer(address indexed from, address indexed to, uint256 value); | |
/** | |
* @dev Emitted when the allowance of a `spender` for an `owner` is set by | |
* a call to {approve}. `value` is the new allowance. | |
*/ | |
event Approval(address indexed owner, address indexed spender, uint256 value); | |
} | |
/** | |
* @dev Implementation of the {IERC20} interface. | |
* | |
* This implementation is agnostic to the way tokens are created. This means | |
* that a supply mechanism has to be added in a derived contract using {_mint}. | |
* For a generic mechanism see {ERC20PresetMinterPauser}. | |
* | |
* TIP: For a detailed writeup see our guide | |
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How | |
* to implement supply mechanisms]. | |
* | |
* We have followed general OpenZeppelin guidelines: functions revert instead | |
* of returning `false` on failure. This behavior is nonetheless conventional | |
* and does not conflict with the expectations of ERC20 applications. | |
* | |
* Additionally, an {Approval} event is emitted on calls to {transferFrom}. | |
* This allows applications to reconstruct the allowance for all accounts just | |
* by listening to said events. Other implementations of the EIP may not emit | |
* these events, as it isn't required by the specification. | |
* | |
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance} | |
* functions have been added to mitigate the well-known issues around setting | |
* allowances. See {IERC20-approve}. | |
*/ | |
contract ERC20 is IERC20 { | |
using SafeMath for uint256; | |
mapping (address => uint256) private _balances; | |
mapping (address => mapping (address => uint256)) private _allowances; | |
uint256 private _totalSupply; | |
string private _name; | |
string private _symbol; | |
uint8 private _decimals; | |
/** | |
* @dev Returns the name of the token. | |
*/ | |
function name() public view returns (string memory) { | |
return _name; | |
} | |
/** | |
* @dev Returns the symbol of the token, usually a shorter version of the | |
* name. | |
*/ | |
function symbol() public view returns (string memory) { | |
return _symbol; | |
} | |
/** | |
* @dev Returns the number of decimals used to get its user representation. | |
* For example, if `decimals` equals `2`, a balance of `505` tokens should | |
* be displayed to a user as `5,05` (`505 / 10 ** 2`). | |
* | |
* Tokens usually opt for a value of 18, imitating the relationship between | |
* Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is | |
* called. | |
* | |
* NOTE: This information is only used for _display_ purposes: it in | |
* no way affects any of the arithmetic of the contract, including | |
* {IERC20-balanceOf} and {IERC20-transfer}. | |
*/ | |
function decimals() public view returns (uint8) { | |
return _decimals; | |
} | |
/** | |
* @dev See {IERC20-totalSupply}. | |
*/ | |
function totalSupply() public view override returns (uint256) { | |
return _totalSupply; | |
} | |
/** | |
* @dev See {IERC20-balanceOf}. | |
*/ | |
function balanceOf(address account) public view override returns (uint256) { | |
return _balances[account]; | |
} | |
/** | |
* @dev See {IERC20-transfer}. | |
* | |
* Requirements: | |
* | |
* - `recipient` cannot be the zero address. | |
* - the caller must have a balance of at least `amount`. | |
*/ | |
function transfer(address recipient, uint256 amount) public virtual override returns (bool) { | |
_transfer(msg.sender, recipient, amount); | |
return true; | |
} | |
/** | |
* @dev See {IERC20-allowance}. | |
*/ | |
function allowance(address owner, address spender) public view virtual override returns (uint256) { | |
return _allowances[owner][spender]; | |
} | |
/** | |
* @dev See {IERC20-approve}. | |
* | |
* Requirements: | |
* | |
* - `spender` cannot be the zero address. | |
*/ | |
function approve(address spender, uint256 amount) public virtual override returns (bool) { | |
_approve(msg.sender, spender, amount); | |
return true; | |
} | |
/** | |
* @dev See {IERC20-transferFrom}. | |
* | |
* Emits an {Approval} event indicating the updated allowance. This is not | |
* required by the EIP. See the note at the beginning of {ERC20}. | |
* | |
* Requirements: | |
* | |
* - `sender` and `recipient` cannot be the zero address. | |
* - `sender` must have a balance of at least `amount`. | |
* - the caller must have allowance for ``sender``'s tokens of at least | |
* `amount`. | |
*/ | |
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { | |
_transfer(sender, recipient, amount); | |
_approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount, "ERC20: transfer amount exceeds allowance")); | |
return true; | |
} | |
/** | |
* @dev Atomically increases the allowance granted to `spender` by the caller. | |
* | |
* This is an alternative to {approve} that can be used as a mitigation for | |
* problems described in {IERC20-approve}. | |
* | |
* Emits an {Approval} event indicating the updated allowance. | |
* | |
* Requirements: | |
* | |
* - `spender` cannot be the zero address. | |
*/ | |
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { | |
_approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)); | |
return true; | |
} | |
/** | |
* @dev Atomically decreases the allowance granted to `spender` by the caller. | |
* | |
* This is an alternative to {approve} that can be used as a mitigation for | |
* problems described in {IERC20-approve}. | |
* | |
* Emits an {Approval} event indicating the updated allowance. | |
* | |
* Requirements: | |
* | |
* - `spender` cannot be the zero address. | |
* - `spender` must have allowance for the caller of at least | |
* `subtractedValue`. | |
*/ | |
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { | |
_approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); | |
return true; | |
} | |
/** | |
* @dev Moves tokens `amount` from `sender` to `recipient`. | |
* | |
* This is internal function is equivalent to {transfer}, and can be used to | |
* e.g. implement automatic token fees, slashing mechanisms, etc. | |
* | |
* Emits a {Transfer} event. | |
* | |
* Requirements: | |
* | |
* - `sender` cannot be the zero address. | |
* - `recipient` cannot be the zero address. | |
* - `sender` must have a balance of at least `amount`. | |
*/ | |
function _transfer(address sender, address recipient, uint256 amount) internal virtual { | |
require(sender != address(0), "ERC20: transfer from the zero address"); | |
require(recipient != address(0), "ERC20: transfer to the zero address"); | |
_beforeTokenTransfer(sender, recipient, amount); | |
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); | |
_balances[recipient] = _balances[recipient].add(amount); | |
emit Transfer(sender, recipient, amount); | |
} | |
/** @dev Creates `amount` tokens and assigns them to `account`, increasing | |
* the total supply. | |
* | |
* Emits a {Transfer} event with `from` set to the zero address. | |
* | |
* Requirements: | |
* | |
* - `to` cannot be the zero address. | |
*/ | |
function _mint(address account, uint256 amount) internal virtual { | |
require(account != address(0), "ERC20: mint to the zero address"); | |
_beforeTokenTransfer(address(0), account, amount); | |
_totalSupply = _totalSupply.add(amount); | |
_balances[account] = _balances[account].add(amount); | |
emit Transfer(address(0), account, amount); | |
} | |
/** | |
* @dev Destroys `amount` tokens from `account`, reducing the | |
* total supply. | |
* | |
* Emits a {Transfer} event with `to` set to the zero address. | |
* | |
* Requirements: | |
* | |
* - `account` cannot be the zero address. | |
* - `account` must have at least `amount` tokens. | |
*/ | |
function _burn(address account, uint256 amount) internal virtual { | |
require(account != address(0), "ERC20: burn from the zero address"); | |
_beforeTokenTransfer(account, address(0), amount); | |
_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); | |
_totalSupply = _totalSupply.sub(amount); | |
emit Transfer(account, address(0), amount); | |
} | |
/** | |
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. | |
* | |
* This internal function is equivalent to `approve`, and can be used to | |
* e.g. set automatic allowances for certain subsystems, etc. | |
* | |
* Emits an {Approval} event. | |
* | |
* Requirements: | |
* | |
* - `owner` cannot be the zero address. | |
* - `spender` cannot be the zero address. | |
*/ | |
function _approve(address owner, address spender, uint256 amount) internal virtual { | |
require(owner != address(0), "ERC20: approve from the zero address"); | |
require(spender != address(0), "ERC20: approve to the zero address"); | |
_allowances[owner][spender] = amount; | |
emit Approval(owner, spender, amount); | |
} | |
function _setupMetaData(string memory name_, string memory symbol_, uint8 decimals_) internal virtual { | |
_name = name_; | |
_symbol = symbol_; | |
_decimals = decimals_; | |
} | |
/** | |
* @dev Hook that is called before any transfer of tokens. This includes | |
* minting and burning. | |
* | |
* Calling conditions: | |
* | |
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens | |
* will be to transferred to `to`. | |
* - when `from` is zero, `amount` tokens will be minted for `to`. | |
* - when `to` is zero, `amount` of ``from``'s tokens will be burned. | |
* - `from` and `to` are never both zero. | |
* | |
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. | |
*/ | |
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } | |
} | |
interface IFxERC20 { | |
function fxManager() external returns(address); | |
function rootToken() external returns(address); | |
function initialize(address _fxManager,address _rootToken, string memory _name, string memory _symbol, uint8 _decimals) external; | |
function deposit(address user, uint256 amount) external; | |
function withdraw(address user, uint256 amount) external; | |
} | |
/** | |
* @title FxERC20 represents fx erc20 | |
*/ | |
contract FxERC20 is IFxERC20, ERC20 { | |
address private _fxManager; | |
address private _rootToken; | |
function initialize(address fxManager_, address rootToken_, string memory name_, string memory symbol_, uint8 decimals_) public override { | |
require(_fxManager == address(0x0) && _rootToken == address(0x0), "Token is already initialized"); | |
_fxManager = fxManager_; | |
_rootToken = rootToken_; | |
// setup meta data | |
setupMetaData(name_, symbol_, decimals_); | |
} | |
// fxManager rturns fx manager | |
function fxManager() public override view returns (address) { | |
return _fxManager; | |
} | |
// rootToken returns root token | |
function rootToken() public override view returns (address) { | |
return _rootToken; | |
} | |
// setup name, symbol and decimals | |
function setupMetaData(string memory _name, string memory _symbol, uint8 _decimals) public { | |
require(msg.sender == _fxManager, "Invalid sender"); | |
_setupMetaData(_name, _symbol, _decimals); | |
} | |
function deposit(address user, uint256 amount) public override { | |
require(msg.sender == _fxManager, "Invalid sender"); | |
_mint(user, amount); | |
} | |
function withdraw(address user, uint256 amount) public override { | |
require(msg.sender == _fxManager, "Invalid sender"); | |
_burn(user, amount); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity 0.7.3; | |
interface IFxERC20 { | |
function fxManager() external returns(address); | |
function rootToken() external returns(address); | |
function initialize(address _fxManager,address _rootToken, string memory _name, string memory _symbol, uint8 _decimals) external; | |
function deposit(address user, uint256 amount) external; | |
function withdraw(address user, uint256 amount) external; | |
} | |
// IFxMessageProcessor represents interface to process message | |
interface IFxMessageProcessor { | |
function onMessageReceive(uint256 stateId, address rootMessageSender, bytes calldata data) external; | |
} | |
/** | |
* @notice Mock child tunnel contract to receive and send message from L2 | |
*/ | |
abstract contract FxBaseChildTunnel is IFxMessageProcessor{ | |
// MessageTunnel on L1 will get data from this event | |
event MessageSent(bytes message); | |
// fx child | |
address public fxChild; | |
constructor(address _fxChild) { | |
fxChild = _fxChild; | |
} | |
function onMessageReceive(uint256 stateId, address rootMessageSender, bytes calldata data) public override { | |
require(msg.sender == fxChild, "Invalid sender"); | |
_processMessageFromRoot(stateId, rootMessageSender, data); | |
} | |
/** | |
* @notice Emit message that can be received on Root Tunnel | |
* @dev Call the internal function when need to emit message | |
* @param message bytes message that will be sent to Root Tunnel | |
* some message examples - | |
* abi.encode(tokenId); | |
* abi.encode(tokenId, tokenMetadata); | |
* abi.encode(messageType, messageData); | |
*/ | |
function _sendMessageToRoot(bytes memory message) internal { | |
emit MessageSent(message); | |
} | |
/** | |
* @notice Process message received from Root Tunnel | |
* @dev function needs to be implemented to handle message as per requirement | |
* This is called by onStateReceive function. | |
* Since it is called via a system call, any event will not be emitted during its execution. | |
* @param stateId unique state id | |
* @param sender root messge sender | |
* @param message bytes message that was sent from Root Tunnel | |
*/ | |
function _processMessageFromRoot(uint256 stateId, address sender, bytes memory message) virtual internal; | |
} | |
/** | |
* @title FxERC20ChildTunnel | |
*/ | |
contract FxERC20ChildTunnel is FxBaseChildTunnel { | |
bytes32 public constant DEPOSIT = keccak256("DEPOSIT"); | |
bytes32 public constant MAP_TOKEN = keccak256("MAP_TOKEN"); | |
string public constant SUFFIX_NAME = " (FXERC20)"; | |
string public constant PREFIX_SYMBOL = "fx"; | |
// event for token maping | |
event TokenMapped(address indexed rootToken, address indexed childToken); | |
// root to child token | |
mapping(address => address) public rootToChildToken; | |
// token template | |
address public tokenTemplate; | |
// token template code hash | |
bytes32 public tokenTemplateCodeHash; | |
constructor(address _fxChild, address _tokenTemplate) FxBaseChildTunnel(_fxChild) { | |
tokenTemplate = _tokenTemplate; | |
require(_isContract(_tokenTemplate), "Token template is not contract"); | |
tokenTemplateCodeHash = keccak256(getMinimalProxyCreationCode(_tokenTemplate)); | |
} | |
function _processMessageFromRoot(uint256 /* stateId */, address /* sender */, bytes memory data) internal override { | |
(bytes32 syncType, bytes memory syncData) = abi.decode(data, (bytes32, bytes)); | |
if (syncType == DEPOSIT) { | |
_syncDeposit(syncData); | |
} else if (syncType == MAP_TOKEN) { | |
_mapToken(syncData); | |
} else { | |
revert("FxERC20ChildTunnel: INVALID_SYNC_TYPE"); | |
} | |
} | |
function withdraw(address childToken, uint256 amount) public { | |
IFxERC20 childTokenContract = IFxERC20(childToken); | |
address rootToken = childTokenContract.rootToken(); | |
// validate root and child token mapping | |
require( | |
childToken != address(0x0) && | |
rootToken != address(0x0) && | |
childToken == rootToChildToken[rootToken], | |
"FxERC20ChildTunnel: NO_MAPPED_TOKEN" | |
); | |
// withdraw tokens | |
childTokenContract.withdraw(msg.sender, amount); | |
// send message to root regarding token burn | |
_sendMessageToRoot(abi.encode(rootToken, childToken, msg.sender, amount)); | |
} | |
// | |
// Internal methods | |
// | |
function _mapToken(bytes memory syncData) public returns (address) { | |
(address rootToken, string memory name, string memory symbol, uint8 decimals) = abi.decode(syncData, (address, string, string, uint8)); | |
// get root to child token | |
address childToken = rootToChildToken[rootToken]; | |
// check if it's already mapped | |
require(childToken == address(0x0), "FxERC20ChildTunnel: ALREADY_MAPPED"); | |
// deploy new child token | |
childToken = _createClone(rootToken, tokenTemplate); | |
IFxERC20(childToken).initialize(address(this), rootToken, string(abi.encodePacked(name, SUFFIX_NAME)), string(abi.encodePacked(PREFIX_SYMBOL, symbol)), decimals); | |
// map the token | |
rootToChildToken[rootToken] = childToken; | |
emit TokenMapped(rootToken, childToken); | |
// return new child token | |
return childToken; | |
} | |
function _syncDeposit(bytes memory syncData) internal { | |
(address rootToken, address depositor, address to, uint256 amount, bytes memory depositData) = abi.decode(syncData, (address, address, address, uint256, bytes)); | |
address childToken = rootToChildToken[rootToken]; | |
// deposit tokens | |
IFxERC20 childTokenContract = IFxERC20(childToken); | |
childTokenContract.deposit(to, amount); | |
// call `onTokenTranfer` on `to` with limit and ignore error | |
if (_isContract(to)) { | |
uint256 txGas = 2000000; | |
bool success = false; | |
bytes memory data = abi.encodeWithSignature("onTokenTransfer(address,address,address,address,uint256,bytes)", rootToken, childToken, depositor, to, amount, depositData); | |
// solium-disable-next-line security/no-inline-assembly | |
assembly { | |
success := call(txGas, to, 0, add(data, 0x20), mload(data), 0, 0) | |
} | |
} | |
} | |
function _createClone(address _rootToken, address _target) internal returns (address _result) { | |
bytes20 _targetBytes = bytes20(_target); | |
bytes32 _salt = keccak256(abi.encodePacked(_rootToken)); | |
assembly { | |
let clone := mload(0x40) | |
mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) | |
mstore(add(clone, 0x14), _targetBytes) | |
mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) | |
_result := create2(0, clone, 0x37, _salt) | |
} | |
require(_result != address(0), "Create2: Failed on minimal deploy"); | |
} | |
// check if address is contract | |
function _isContract(address _addr) private view returns (bool){ | |
uint32 size; | |
assembly { | |
size := extcodesize(_addr) | |
} | |
return (size > 0); | |
} | |
// get minimal proxy createion code | |
function getMinimalProxyCreationCode(address _logic) public pure returns (bytes memory) { | |
bytes10 creation = 0x3d602d80600a3d3981f3; | |
bytes10 prefix = 0x363d3d373d3d3d363d73; | |
bytes20 targetBytes = bytes20(_logic); | |
bytes15 suffix = 0x5af43d82803e903d91602b57fd5bf3; | |
return abi.encodePacked(creation, prefix, targetBytes, suffix); | |
} | |
function getComputedChildToken(address _rootToken) public view returns (address) { | |
bytes32 _salt = keccak256(abi.encodePacked(_rootToken)); | |
bytes32 _data = keccak256( | |
abi.encodePacked(bytes1(0xff), address(this), _salt, tokenTemplateCodeHash) | |
); | |
return address(uint160(uint256(_data))); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity 0.7.3; | |
interface IStateSender { | |
function syncState(address receiver, bytes calldata data) external; | |
} | |
interface IFxStateSender { | |
function sendMessageToChild(address _receiver, bytes calldata _data) external; | |
} | |
/** | |
* @title FxRoot root contract for fx-portal | |
*/ | |
contract FxRoot is IFxStateSender { | |
IStateSender public stateSender; | |
address public fxChild; | |
constructor(address _stateSender) { | |
stateSender = IStateSender(_stateSender); | |
} | |
function setFxChild(address _fxChild) public { | |
require(fxChild == address(0x0)); | |
fxChild = _fxChild; | |
} | |
function sendMessageToChild(address _receiver, bytes calldata _data) public override { | |
bytes memory data = abi.encode(msg.sender, _receiver, _data); | |
stateSender.syncState(fxChild, data); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity 0.7.0; | |
interface IStateSender { | |
function syncState(address receiver, bytes calldata data) external; | |
} | |
/** | |
* @title FxRoot | |
*/ | |
contract FxRoot { | |
// address public fxChild = address(0xe5093D4131d67Dc6409f2e2756F15b959Ac70E62); | |
IStateSender public stateSender = IStateSender(0xEAa852323826C71cd7920C3b4c007184234c3945); | |
function sendMessageToChild(address fxChild, address _receiver, bytes calldata _data) external { | |
bytes memory data = abi.encode(msg.sender, _receiver, _data); | |
stateSender.syncState(fxChild, data); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Sources flattened with hardhat v2.0.7 https://hardhat.org | |
// File contracts/tunnel/FxBaseChildTunnel.sol | |
pragma solidity 0.7.3; | |
// IFxMessageProcessor represents interface to process message | |
interface IFxMessageProcessor { | |
function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes calldata data) external; | |
} | |
/** | |
* @notice Mock child tunnel contract to receive and send message from L2 | |
*/ | |
abstract contract FxBaseChildTunnel is IFxMessageProcessor{ | |
// MessageTunnel on L1 will get data from this event | |
event MessageSent(bytes message); | |
// fx child | |
address public fxChild; | |
// fx root tunnel | |
address public fxRootTunnel; | |
constructor(address _fxChild) { | |
fxChild = _fxChild; | |
} | |
// Sender must be fxRootTunnel in case of ERC20 tunnel | |
modifier validateSender(address sender) { | |
require(sender == fxRootTunnel, "FxBaseChildTunnel: INVALID_SENDER_FROM_ROOT"); | |
_; | |
} | |
// set fxRootTunnel if not set already | |
function setFxRootTunnel(address _fxRootTunnel) public { | |
require(fxRootTunnel == address(0x0), "FxBaseChildTunnel: ROOT_TUNNEL_ALREADY_SET"); | |
fxRootTunnel = _fxRootTunnel; | |
} | |
function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes calldata data) public override { | |
require(msg.sender == fxChild, "FxBaseChildTunnel: INVALID_SENDER"); | |
_processMessageFromRoot(stateId, rootMessageSender, data); | |
} | |
/** | |
* @notice Emit message that can be received on Root Tunnel | |
* @dev Call the internal function when need to emit message | |
* @param message bytes message that will be sent to Root Tunnel | |
* some message examples - | |
* abi.encode(tokenId); | |
* abi.encode(tokenId, tokenMetadata); | |
* abi.encode(messageType, messageData); | |
*/ | |
function _sendMessageToRoot(bytes memory message) internal { | |
emit MessageSent(message); | |
} | |
/** | |
* @notice Process message received from Root Tunnel | |
* @dev function needs to be implemented to handle message as per requirement | |
* This is called by onStateReceive function. | |
* Since it is called via a system call, any event will not be emitted during its execution. | |
* @param stateId unique state id | |
* @param sender root messge sender | |
* @param message bytes message that was sent from Root Tunnel | |
*/ | |
function _processMessageFromRoot(uint256 stateId, address sender, bytes memory message) virtual internal; | |
} | |
// File contracts/examples/state-transfer/FxStateChildTunnel.sol | |
// SPDX-License-Identifier: MIT | |
pragma solidity 0.7.3; | |
/** | |
* @title FxStateChildTunnel | |
*/ | |
contract FxStateChildTunnel is FxBaseChildTunnel { | |
uint256 public latestStateId; | |
address public latestRootMessageSender; | |
bytes public latestData; | |
constructor(address _fxChild) FxBaseChildTunnel(_fxChild) { | |
} | |
function _processMessageFromRoot(uint256 stateId, address sender, bytes memory data) | |
internal | |
override | |
validateSender(sender) { | |
latestStateId = stateId; | |
latestRootMessageSender = sender; | |
latestData = data; | |
} | |
function sendMessageToRoot(bytes memory message) public { | |
_sendMessageToRoot(message); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Sources flattened with hardhat v2.0.7 https://hardhat.org | |
// File contracts/lib/RLPReader.sol | |
pragma solidity 0.7.3; | |
library RLPReader { | |
uint8 constant STRING_SHORT_START = 0x80; | |
uint8 constant STRING_LONG_START = 0xb8; | |
uint8 constant LIST_SHORT_START = 0xc0; | |
uint8 constant LIST_LONG_START = 0xf8; | |
uint8 constant WORD_SIZE = 32; | |
struct RLPItem { | |
uint256 len; | |
uint256 memPtr; | |
} | |
/* | |
* @param item RLP encoded bytes | |
*/ | |
function toRlpItem(bytes memory item) | |
internal | |
pure | |
returns (RLPItem memory) | |
{ | |
require(item.length > 0, "RLPReader: INVALID_BYTES_LENGTH"); | |
uint256 memPtr; | |
assembly { | |
memPtr := add(item, 0x20) | |
} | |
return RLPItem(item.length, memPtr); | |
} | |
/* | |
* @param item RLP encoded list in bytes | |
*/ | |
function toList(RLPItem memory item) | |
internal | |
pure | |
returns (RLPItem[] memory) | |
{ | |
require(isList(item), "RLPReader: ITEM_NOT_LIST"); | |
uint256 items = numItems(item); | |
RLPItem[] memory result = new RLPItem[](items); | |
uint256 listLength = _itemLength(item.memPtr); | |
require(listLength == item.len, "RLPReader: LIST_DECODED_LENGTH_MISMATCH"); | |
uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr); | |
uint256 dataLen; | |
for (uint256 i = 0; i < items; i++) { | |
dataLen = _itemLength(memPtr); | |
result[i] = RLPItem(dataLen, memPtr); | |
memPtr = memPtr + dataLen; | |
} | |
return result; | |
} | |
// @return indicator whether encoded payload is a list. negate this function call for isData. | |
function isList(RLPItem memory item) internal pure returns (bool) { | |
uint8 byte0; | |
uint256 memPtr = item.memPtr; | |
assembly { | |
byte0 := byte(0, mload(memPtr)) | |
} | |
if (byte0 < LIST_SHORT_START) return false; | |
return true; | |
} | |
/** RLPItem conversions into data types **/ | |
// @returns raw rlp encoding in bytes | |
function toRlpBytes(RLPItem memory item) | |
internal | |
pure | |
returns (bytes memory) | |
{ | |
bytes memory result = new bytes(item.len); | |
uint256 ptr; | |
assembly { | |
ptr := add(0x20, result) | |
} | |
copy(item.memPtr, ptr, item.len); | |
return result; | |
} | |
function toAddress(RLPItem memory item) internal pure returns (address) { | |
require(!isList(item), "RLPReader: DECODING_LIST_AS_ADDRESS"); | |
// 1 byte for the length prefix | |
require(item.len == 21, "RLPReader: INVALID_ADDRESS_LENGTH"); | |
return address(toUint(item)); | |
} | |
function toUint(RLPItem memory item) internal pure returns (uint256) { | |
require(!isList(item), "RLPReader: DECODING_LIST_AS_UINT"); | |
require(item.len <= 33, "RLPReader: INVALID_UINT_LENGTH"); | |
uint256 itemLength = _itemLength(item.memPtr); | |
require(itemLength == item.len, "RLPReader: UINT_DECODED_LENGTH_MISMATCH"); | |
uint256 offset = _payloadOffset(item.memPtr); | |
uint256 len = item.len - offset; | |
uint256 result; | |
uint256 memPtr = item.memPtr + offset; | |
assembly { | |
result := mload(memPtr) | |
// shfit to the correct location if neccesary | |
if lt(len, 32) { | |
result := div(result, exp(256, sub(32, len))) | |
} | |
} | |
return result; | |
} | |
// enforces 32 byte length | |
function toUintStrict(RLPItem memory item) internal pure returns (uint256) { | |
uint256 itemLength = _itemLength(item.memPtr); | |
require(itemLength == item.len, "RLPReader: UINT_STRICT_DECODED_LENGTH_MISMATCH"); | |
// one byte prefix | |
require(item.len == 33, "RLPReader: INVALID_UINT_STRICT_LENGTH"); | |
uint256 result; | |
uint256 memPtr = item.memPtr + 1; | |
assembly { | |
result := mload(memPtr) | |
} | |
return result; | |
} | |
function toBytes(RLPItem memory item) internal pure returns (bytes memory) { | |
uint256 listLength = _itemLength(item.memPtr); | |
require(listLength == item.len, "RLPReader: BYTES_DECODED_LENGTH_MISMATCH"); | |
uint256 offset = _payloadOffset(item.memPtr); | |
uint256 len = item.len - offset; // data length | |
bytes memory result = new bytes(len); | |
uint256 destPtr; | |
assembly { | |
destPtr := add(0x20, result) | |
} | |
copy(item.memPtr + offset, destPtr, len); | |
return result; | |
} | |
/* | |
* Private Helpers | |
*/ | |
// @return number of payload items inside an encoded list. | |
function numItems(RLPItem memory item) private pure returns (uint256) { | |
// add `isList` check if `item` is expected to be passsed without a check from calling function | |
// require(isList(item), "RLPReader: NUM_ITEMS_NOT_LIST"); | |
uint256 count = 0; | |
uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr); | |
uint256 endPtr = item.memPtr + item.len; | |
while (currPtr < endPtr) { | |
currPtr = currPtr + _itemLength(currPtr); // skip over an item | |
require(currPtr <= endPtr, "RLPReader: NUM_ITEMS_DECODED_LENGTH_MISMATCH"); | |
count++; | |
} | |
return count; | |
} | |
// @return entire rlp item byte length | |
function _itemLength(uint256 memPtr) private pure returns (uint256) { | |
uint256 itemLen; | |
uint256 byte0; | |
assembly { | |
byte0 := byte(0, mload(memPtr)) | |
} | |
if (byte0 < STRING_SHORT_START) itemLen = 1; | |
else if (byte0 < STRING_LONG_START) | |
itemLen = byte0 - STRING_SHORT_START + 1; | |
else if (byte0 < LIST_SHORT_START) { | |
assembly { | |
let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is | |
memPtr := add(memPtr, 1) // skip over the first byte | |
/* 32 byte word size */ | |
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len | |
itemLen := add(dataLen, add(byteLen, 1)) | |
} | |
} else if (byte0 < LIST_LONG_START) { | |
itemLen = byte0 - LIST_SHORT_START + 1; | |
} else { | |
assembly { | |
let byteLen := sub(byte0, 0xf7) | |
memPtr := add(memPtr, 1) | |
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length | |
itemLen := add(dataLen, add(byteLen, 1)) | |
} | |
} | |
return itemLen; | |
} | |
// @return number of bytes until the data | |
function _payloadOffset(uint256 memPtr) private pure returns (uint256) { | |
uint256 byte0; | |
assembly { | |
byte0 := byte(0, mload(memPtr)) | |
} | |
if (byte0 < STRING_SHORT_START) return 0; | |
else if ( | |
byte0 < STRING_LONG_START || | |
(byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START) | |
) return 1; | |
else if (byte0 < LIST_SHORT_START) | |
// being explicit | |
return byte0 - (STRING_LONG_START - 1) + 1; | |
else return byte0 - (LIST_LONG_START - 1) + 1; | |
} | |
/* | |
* @param src Pointer to source | |
* @param dest Pointer to destination | |
* @param len Amount of memory to copy from the source | |
*/ | |
function copy( | |
uint256 src, | |
uint256 dest, | |
uint256 len | |
) private pure { | |
if (len == 0) return; | |
// copy as many word sizes as possible | |
for (; len >= WORD_SIZE; len -= WORD_SIZE) { | |
assembly { | |
mstore(dest, mload(src)) | |
} | |
src += WORD_SIZE; | |
dest += WORD_SIZE; | |
} | |
// left over bytes. Mask is used to remove unwanted bytes from the word | |
uint256 mask = 256**(WORD_SIZE - len) - 1; | |
assembly { | |
let srcpart := and(mload(src), not(mask)) // zero out src | |
let destpart := and(mload(dest), mask) // retrieve the bytes | |
mstore(dest, or(destpart, srcpart)) | |
} | |
} | |
} | |
// File contracts/lib/MerklePatriciaProof.sol | |
pragma solidity 0.7.3; | |
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. | |
* @param rlpParentNodes The rlp encoded stack of nodes. | |
* @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; | |
uint256 pathPtr = 0; | |
bytes memory path = _getNibbleArray(encodedPath); | |
if (path.length == 0) { | |
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) { | |
uint256 traversed = _nibblesToTraverse( | |
RLPReader.toBytes(currentNodeList[0]), | |
path, | |
pathPtr | |
); | |
if (pathPtr + traversed == path.length) { | |
//leaf node | |
if ( | |
keccak256(RLPReader.toBytes(currentNodeList[1])) == | |
keccak256(value) | |
) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
//extension node | |
if (traversed == 0) { | |
return false; | |
} | |
pathPtr += traversed; | |
nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[1])); | |
} else { | |
return false; | |
} | |
} | |
} | |
function _nibblesToTraverse( | |
bytes memory encodedPartialPath, | |
bytes memory path, | |
uint256 pathPtr | |
) private pure returns (uint256) { | |
uint256 len = 0; | |
// 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; | |
} | |
if (keccak256(partialPath) == keccak256(slicedPath)) { | |
len = partialPath.length; | |
} else { | |
len = 0; | |
} | |
return len; | |
} | |
// bytes b must be hp encoded | |
function _getNibbleArray(bytes memory b) | |
internal | |
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; | |
} | |
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 | |
); | |
} | |
} | |
// File contracts/lib/Merkle.sol | |
pragma solidity 0.7.3; | |
library Merkle { | |
function checkMembership( | |
bytes32 leaf, | |
uint256 index, | |
bytes32 rootHash, | |
bytes memory proof | |
) internal pure returns (bool) { | |
require(proof.length % 32 == 0, "Invalid proof length"); | |
uint256 proofHeight = proof.length / 32; | |
// Proof of size n means, height of the tree is n+1. | |
// In a tree of height n+1, max #leafs possible is 2 ^ n | |
require(index < 2 ** proofHeight, "Leaf index is too big"); | |
bytes32 proofElement; | |
bytes32 computedHash = leaf; | |
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) | |
); | |
} | |
index = index / 2; | |
} | |
return computedHash == rootHash; | |
} | |
} | |
// File contracts/tunnel/FxBaseRootTunnel.sol | |
pragma solidity 0.7.3; | |
interface IFxStateSender { | |
function sendMessageToChild(address _receiver, bytes calldata _data) external; | |
} | |
contract ICheckpointManager { | |
struct HeaderBlock { | |
bytes32 root; | |
uint256 start; | |
uint256 end; | |
uint256 createdAt; | |
address proposer; | |
} | |
/** | |
* @notice mapping of checkpoint header numbers to block details | |
* @dev These checkpoints are submited by plasma contracts | |
*/ | |
mapping(uint256 => HeaderBlock) public headerBlocks; | |
} | |
abstract contract FxBaseRootTunnel { | |
using RLPReader for bytes; | |
using RLPReader for RLPReader.RLPItem; | |
using Merkle for bytes32; | |
// keccak256(MessageSent(bytes)) | |
bytes32 public constant SEND_MESSAGE_EVENT_SIG = 0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036; | |
// state sender contract | |
IFxStateSender public fxRoot; | |
// root chain manager | |
ICheckpointManager public checkpointManager; | |
// child tunnel contract which receives and sends messages | |
address public fxChildTunnel; | |
// storage to avoid duplicate exits | |
mapping(bytes32 => bool) public processedExits; | |
constructor(address _checkpointManager, address _fxRoot) { | |
checkpointManager = ICheckpointManager(_checkpointManager); | |
fxRoot = IFxStateSender(_fxRoot); | |
} | |
// set fxChildTunnel if not set already | |
function setFxChildTunnel(address _fxChildTunnel) public { | |
require(fxChildTunnel == address(0x0), "FxBaseRootTunnel: CHILD_TUNNEL_ALREADY_SET"); | |
fxChildTunnel = _fxChildTunnel; | |
} | |
/** | |
* @notice Send bytes message to Child Tunnel | |
* @param message bytes message that will be sent to Child Tunnel | |
* some message examples - | |
* abi.encode(tokenId); | |
* abi.encode(tokenId, tokenMetadata); | |
* abi.encode(messageType, messageData); | |
*/ | |
function _sendMessageToChild(bytes memory message) internal { | |
fxRoot.sendMessageToChild(fxChildTunnel, message); | |
} | |
function _validateAndExtractMessage(bytes memory inputData) internal returns (bytes memory) { | |
RLPReader.RLPItem[] memory inputDataRLPList = inputData | |
.toRlpItem() | |
.toList(); | |
// checking if exit has already been processed | |
// unique exit is identified using hash of (blockNumber, branchMask, receiptLogIndex) | |
bytes32 exitHash = keccak256( | |
abi.encodePacked( | |
inputDataRLPList[2].toUint(), // blockNumber | |
// first 2 nibbles are dropped while generating nibble array | |
// this allows branch masks that are valid but bypass exitHash check (changing first 2 nibbles only) | |
// so converting to nibble array and then hashing it | |
MerklePatriciaProof._getNibbleArray(inputDataRLPList[8].toBytes()), // branchMask | |
inputDataRLPList[9].toUint() // receiptLogIndex | |
) | |
); | |
require( | |
processedExits[exitHash] == false, | |
"FxRootTunnel: EXIT_ALREADY_PROCESSED" | |
); | |
processedExits[exitHash] = true; | |
RLPReader.RLPItem[] memory receiptRLPList = inputDataRLPList[6] | |
.toBytes() | |
.toRlpItem() | |
.toList(); | |
RLPReader.RLPItem memory logRLP = receiptRLPList[3] | |
.toList()[ | |
inputDataRLPList[9].toUint() // receiptLogIndex | |
]; | |
RLPReader.RLPItem[] memory logRLPList = logRLP.toList(); | |
// check child tunnel | |
require(fxChildTunnel == RLPReader.toAddress(logRLPList[0]), "FxRootTunnel: INVALID_FX_CHILD_TUNNEL"); | |
// verify receipt inclusion | |
require( | |
MerklePatriciaProof.verify( | |
inputDataRLPList[6].toBytes(), // receipt | |
inputDataRLPList[8].toBytes(), // branchMask | |
inputDataRLPList[7].toBytes(), // receiptProof | |
bytes32(inputDataRLPList[5].toUint()) // receiptRoot | |
), | |
"FxRootTunnel: INVALID_RECEIPT_PROOF" | |
); | |
// verify checkpoint inclusion | |
_checkBlockMembershipInCheckpoint( | |
inputDataRLPList[2].toUint(), // blockNumber | |
inputDataRLPList[3].toUint(), // blockTime | |
bytes32(inputDataRLPList[4].toUint()), // txRoot | |
bytes32(inputDataRLPList[5].toUint()), // receiptRoot | |
inputDataRLPList[0].toUint(), // headerNumber | |
inputDataRLPList[1].toBytes() // blockProof | |
); | |
RLPReader.RLPItem[] memory logTopicRLPList = logRLPList[1].toList(); // topics | |
require( | |
bytes32(logTopicRLPList[0].toUint()) == SEND_MESSAGE_EVENT_SIG, // topic0 is event sig | |
"FxRootTunnel: INVALID_SIGNATURE" | |
); | |
// received message data | |
bytes memory receivedData = logRLPList[2].toBytes(); | |
(bytes memory message) = abi.decode(receivedData, (bytes)); // event decodes params again, so decoding bytes to get message | |
return message; | |
} | |
function _checkBlockMembershipInCheckpoint( | |
uint256 blockNumber, | |
uint256 blockTime, | |
bytes32 txRoot, | |
bytes32 receiptRoot, | |
uint256 headerNumber, | |
bytes memory blockProof | |
) private view returns (uint256) { | |
( | |
bytes32 headerRoot, | |
uint256 startBlock, | |
, | |
uint256 createdAt, | |
) = checkpointManager.headerBlocks(headerNumber); | |
require( | |
keccak256( | |
abi.encodePacked(blockNumber, blockTime, txRoot, receiptRoot) | |
) | |
.checkMembership( | |
blockNumber-startBlock, | |
headerRoot, | |
blockProof | |
), | |
"FxRootTunnel: INVALID_HEADER" | |
); | |
return createdAt; | |
} | |
/** | |
* @notice receive message from L2 to L1, validated by proof | |
* @dev This function verifies if the transaction actually happened on child chain | |
* | |
* @param inputData RLP encoded data of the reference tx containing following list of fields | |
* 0 - headerNumber - Checkpoint header block number containing the reference tx | |
* 1 - blockProof - Proof that the block header (in the child chain) is a leaf in the submitted merkle root | |
* 2 - blockNumber - Block number containing the reference tx on child chain | |
* 3 - blockTime - Reference tx block time | |
* 4 - txRoot - Transactions root of block | |
* 5 - receiptRoot - Receipts root of block | |
* 6 - receipt - Receipt of the reference transaction | |
* 7 - receiptProof - Merkle proof of the reference receipt | |
* 8 - branchMask - 32 bits denoting the path of receipt in merkle tree | |
* 9 - receiptLogIndex - Log Index to read from the receipt | |
*/ | |
function receiveMessage(bytes memory inputData) public virtual { | |
bytes memory message = _validateAndExtractMessage(inputData); | |
_processMessageFromChild(message); | |
} | |
/** | |
* @notice Process message received from Child Tunnel | |
* @dev function needs to be implemented to handle message as per requirement | |
* This is called by onStateReceive function. | |
* Since it is called via a system call, any event will not be emitted during its execution. | |
* @param message bytes message that was sent from Child Tunnel | |
*/ | |
function _processMessageFromChild(bytes memory message) virtual internal; | |
} | |
// File contracts/examples/state-transfer/FxStateRootTunnel.sol | |
// SPDX-License-Identifier: MIT | |
pragma solidity 0.7.3; | |
/** | |
* @title FxStateRootTunnel | |
*/ | |
contract FxStateRootTunnel is FxBaseRootTunnel { | |
bytes public latestData; | |
constructor(address _checkpointManager, address _fxRoot) FxBaseRootTunnel(_checkpointManager, _fxRoot) {} | |
function _processMessageFromChild(bytes memory data) internal override { | |
latestData = data; | |
} | |
function sendMessageToChild(bytes memory message) public { | |
_sendMessageToChild(message); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity 0.7.0; | |
// IStateReceiver represents interface to receive state | |
interface IStateReceiver { | |
function onStateReceive(uint256 stateId, bytes calldata data) external; | |
} | |
// IFxMessageProcessor represents interface to process message | |
interface IFxMessageProcessor { | |
function onMessageReceive(uint256 stateId, address rootMessageSender, bytes calldata data) external; | |
} | |
/** | |
* @title FxChild | |
*/ | |
contract FxChild is IStateReceiver { | |
event NewFxMessage(address rootMessageSender, address receiver, bytes data); | |
function onStateReceive(uint256 stateId, bytes calldata _data) external override { | |
require(msg.sender == address(0x0000000000000000000000000000000000001001), "Invalid sender"); | |
(address rootMessageSender, address receiver, bytes memory data) = abi.decode(_data, (address, address, bytes)); | |
emit NewFxMessage(rootMessageSender, receiver, data); | |
IFxMessageProcessor(receiver).onMessageReceive(stateId, rootMessageSender, data); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment