Skip to content

Instantly share code, notes, and snippets.

@jdkanani
Created February 12, 2021 09:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdkanani/b50a818374d3e96f7a9aaf9b96c3780b to your computer and use it in GitHub Desktop.
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=
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;
}
}
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;
}
}
// 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);
}
}
// 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;
}
}
// 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");
}
}
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));
// }
}
// 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);
}
}
// 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);
}
}
// 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)));
}
}
// 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);
}
}
// 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);
}
}
// 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);
}
}
// 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);
}
}
// 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