Skip to content

Instantly share code, notes, and snippets.

@willwillis
Forked from HaoyangLiu/ABCToken.sol
Created November 26, 2020 02:01
Show Gist options
  • Select an option

  • Save willwillis/f94e6796356cfdceac2334ba5ccce616 to your computer and use it in GitHub Desktop.

Select an option

Save willwillis/f94e6796356cfdceac2334ba5ccce616 to your computer and use it in GitHub Desktop.

Revisions

  1. HaoyangLiu revised this gist Apr 29, 2020. 8 changed files with 26 additions and 26 deletions.
    8 changes: 4 additions & 4 deletions ABCToken.sol
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,9 @@
    pragma solidity 0.6.4;

    import "Context.sol";
    import "IERC20.sol";
    import "SafeMath.sol";
    import "Ownable.sol";
    import "./Context.sol";
    import "./IERC20.sol";
    import "./SafeMath.sol";
    import "./Ownable.sol";

    /**
    * @dev Implementation of the {IERC20} interface.
    8 changes: 4 additions & 4 deletions DEFToken.sol
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,9 @@
    pragma solidity 0.6.4;

    import "Context.sol";
    import "IERC20.sol";
    import "SafeMath.sol";
    import "Ownable.sol";
    import "./Context.sol";
    import "./IERC20.sol";
    import "./SafeMath.sol";
    import "./Ownable.sol";

    /**
    * @dev Implementation of the {IERC20} interface.
    2 changes: 1 addition & 1 deletion MerkleProof.sol
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    pragma solidity 0.6.4;

    import "Memory.sol";
    import "./Memory.sol";

    library MerkleProof {
    function validateMerkleProof(bytes32 appHash, string memory storeName, bytes memory key,
    2 changes: 1 addition & 1 deletion Ownable.sol
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    pragma solidity 0.6.4;

    import "Context.sol";
    import "./Context.sol";
    /**
    * @dev Contract module which provides a basic access control mechanism, where
    * there is an account (an owner) that can be granted exclusive access to
    2 changes: 1 addition & 1 deletion RelayerIncentivize.sol
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    pragma solidity 0.6.4;

    import "IRelayerIncentivize.sol";
    import "./IRelayerIncentivize.sol";


    contract RelayerIncentivize is IRelayerIncentivize {
    8 changes: 4 additions & 4 deletions TendermintLightClient.sol
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,9 @@
    pragma solidity 0.6.4;

    import "Memory.sol";
    import "ILightClient.sol";
    import "ISystemReward.sol";
    import "IRelayerHub.sol";
    import "./Memory.sol";
    import "./ILightClient.sol";
    import "./ISystemReward.sol";
    import "./IRelayerHub.sol";

    contract TendermintLightClient is ILightClient {

    14 changes: 7 additions & 7 deletions TokenHub.sol
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,12 @@
    pragma solidity 0.6.4;

    import "IERC20.sol";
    import "ILightClient.sol";
    import "IRelayerIncentivize.sol";
    import "MerkleProof.sol";
    import "ISystemReward.sol";
    import "ITokenHub.sol";
    import "IRelayerHub.sol";
    import "./IERC20.sol";
    import "./ILightClient.sol";
    import "./IRelayerIncentivize.sol";
    import "./MerkleProof.sol";
    import "./ISystemReward.sol";
    import "./ITokenHub.sol";
    import "./IRelayerHub.sol";

    contract TokenHub is ITokenHub {

    8 changes: 4 additions & 4 deletions XYZToken.sol
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,9 @@
    pragma solidity 0.6.4;

    import "Context.sol";
    import "IERC20.sol";
    import "SafeMath.sol";
    import "Ownable.sol";
    import "./Context.sol";
    import "./IERC20.sol";
    import "./SafeMath.sol";
    import "./Ownable.sol";

    /**
    * @dev Implementation of the {IERC20} interface.
  2. HaoyangLiu created this gist Apr 29, 2020.
    265 changes: 265 additions & 0 deletions ABCToken.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,265 @@
    pragma solidity 0.6.4;

    import "Context.sol";
    import "IERC20.sol";
    import "SafeMath.sol";
    import "Ownable.sol";

    /**
    * @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 {ERC20Mintable}.
    *
    * 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 ABCToken is Context, IERC20, Ownable {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;
    uint8 public _decimals;
    string public _symbol;
    string public _name;

    constructor() public {
    _name = "ABC token";
    _symbol = "abc";
    _decimals = 18;
    _totalSupply = 100000000000000000000000000;
    _balances[msg.sender] = _totalSupply;

    emit Transfer(address(0), msg.sender, _totalSupply);
    }

    /**
    * @dev Returns the bep token owner.
    */
    function getOwner() external override view returns (address) {
    return owner();
    }

    /**
    * @dev Returns the token decimals.
    */
    function decimals() external override view returns (uint256) {
    return _decimals;
    }

    /**
    * @dev Returns the contract owner.
    */
    function symbol() external override view returns (string memory) {
    return _symbol;
    }

    /**
    * @dev See {IERC20-totalSupply}.
    */
    function totalSupply() external override view returns (uint256) {
    return _totalSupply;
    }

    /**
    * @dev See {IERC20-balanceOf}.
    */
    function balanceOf(address account) external override view 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) external override returns (bool) {
    _transfer(_msgSender(), recipient, amount);
    return true;
    }

    /**
    * @dev See {IERC20-allowance}.
    */
    function allowance(address owner, address spender) external override view returns (uint256) {
    return _allowances[owner][spender];
    }

    /**
    * @dev See {IERC20-approve}.
    *
    * Requirements:
    *
    * - `spender` cannot be the zero address.
    */
    function approve(address spender, uint256 amount) external override returns (bool) {
    _approve(_msgSender(), 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) external override returns (bool) {
    _transfer(sender, recipient, amount);
    _approve(sender, _msgSender(), _allowances[sender][_msgSender()].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 returns (bool) {
    _approve(_msgSender(), spender, _allowances[_msgSender()][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 returns (bool) {
    _approve(_msgSender(), spender, _allowances[_msgSender()][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 {
    require(sender != address(0), "ERC20: transfer from the zero address");
    require(recipient != address(0), "ERC20: transfer to the zero address");

    _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 {
    require(account != address(0), "ERC20: mint to the zero address");

    _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 {
    require(account != address(0), "ERC20: burn from the zero address");

    _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 is 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 {
    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);
    }

    /**
    * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
    * from the caller's allowance.
    *
    * See {_burn} and {_approve}.
    */
    function _burnFrom(address account, uint256 amount) internal {
    _burn(account, amount);
    _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
    }
    }
    27 changes: 27 additions & 0 deletions Context.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@
    pragma solidity 0.6.4;

    /*
    * @dev Provides information about the current execution context, including the
    * sender of the transaction and its data. While these are generally available
    * via msg.sender and msg.data, they should not be accessed in such a direct
    * manner, since when dealing with GSN meta-transactions the account sending and
    * paying for execution may not be the actual sender (as far as an application
    * is concerned).
    *
    * This contract is only required for intermediate, library-like contracts.
    */
    contract Context {
    // Empty internal constructor, to prevent people from mistakenly deploying
    // an instance of this contract, which should be used via inheritance.
    constructor () internal { }
    // solhint-disable-previous-line no-empty-blocks

    function _msgSender() internal view returns (address payable) {
    return msg.sender;
    }

    function _msgData() internal view returns (bytes memory) {
    this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
    return msg.data;
    }
    }
    257 changes: 257 additions & 0 deletions DEFToken.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,257 @@
    pragma solidity 0.6.4;

    import "Context.sol";
    import "IERC20.sol";
    import "SafeMath.sol";
    import "Ownable.sol";

    /**
    * @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 {ERC20Mintable}.
    *
    * 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 DEFToken is Context, IERC20, Ownable {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;
    uint8 public _decimals;
    string public _symbol;
    string public _name;

    constructor() public {
    _name = "DEF token";
    _symbol = "DEF";
    _decimals = 18;
    _totalSupply = 100000000000000000000000000;
    _balances[msg.sender] = _totalSupply;

    emit Transfer(address(0), msg.sender, _totalSupply);
    }

    /**
    * @dev Returns the address of the current owner.
    */
    function getOwner() override public view returns (address) {
    return owner();
    }

    /**
    * @dev Returns the token decimals.
    */
    function decimals() override external view returns (uint256) {
    return _decimals;
    }

    /**
    * @dev Returns the contract owner.
    */
    function symbol() override external view returns (string memory) {
    return _symbol;
    }

    /**
    * @dev See {IERC20-totalSupply}.
    */
    function totalSupply() override public view returns (uint256) {
    return _totalSupply;
    }

    /**
    * @dev See {IERC20-balanceOf}.
    */
    function balanceOf(address account) override public view returns (uint256) {
    return _balances[account];
    }

    function transfer(address recipient, uint256 amount) override public returns (bool) {
    _transfer(_msgSender(), recipient, amount);
    return true;
    }

    /**
    * @dev See {IERC20-allowance}.
    */
    function allowance(address owner, address spender) override public view returns (uint256) {
    return _allowances[owner][spender];
    }

    /**
    * @dev See {IERC20-approve}.
    *
    * Requirements:
    *
    * - `spender` cannot be the zero address.
    */
    function approve(address spender, uint256 amount) override public returns (bool) {
    _approve(_msgSender(), 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) override public returns (bool) {
    _transfer(sender, recipient, amount);
    _approve(sender, _msgSender(), _allowances[sender][_msgSender()].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 returns (bool) {
    _approve(_msgSender(), spender, _allowances[_msgSender()][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 returns (bool) {
    _approve(_msgSender(), spender, _allowances[_msgSender()][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 {
    require(sender != address(0), "ERC20: transfer from the zero address");
    require(recipient != address(0), "ERC20: transfer to the zero address");

    _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 {
    require(account != address(0), "ERC20: mint to the zero address");

    _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 {
    require(account != address(0), "ERC20: burn from the zero address");

    _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 is 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 {
    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);
    }

    /**
    * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
    * from the caller's allowance.
    *
    * See {_burn} and {_approve}.
    */
    function _burnFrom(address account, uint256 amount) internal {
    _burn(account, amount);
    _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
    }
    }
    91 changes: 91 additions & 0 deletions IERC20.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,91 @@
    pragma solidity 0.6.4;

    /**
    * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
    * the optional functions; to access them see {ERC20Detailed}.
    */
    interface IERC20 {
    /**
    * @dev Returns the amount of tokens in existence.
    */
    function totalSupply() external view returns (uint256);

    /**
    * @dev Returns the token decimals.
    */
    function decimals() external view returns (uint256);

    /**
    * @dev Returns the contract owner.
    */
    function symbol() external view returns (string memory);

    /**
    * @dev Returns the bep token owner.
    */
    function getOwner() external view returns (address);

    /**
    * @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);
    }
    11 changes: 11 additions & 0 deletions ILightClient.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    pragma solidity 0.6.4;

    interface ILightClient {

    function getAppHash(uint64 height) external view returns (bytes32);

    function isHeaderSynced(uint64 height) external view returns (bool);

    function getSubmitter(uint64 height) external view returns (address payable);

    }
    5 changes: 5 additions & 0 deletions IRelayerHub.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    pragma solidity 0.6.4;

    interface IRelayerHub {
    function isRelayer(address sender) external view returns (bool);
    }
    7 changes: 7 additions & 0 deletions IRelayerIncentivize.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    pragma solidity 0.6.4;

    interface IRelayerIncentivize {

    function addReward(address payable headerRelayerAddr, address payable caller) external payable returns (bool);

    }
    5 changes: 5 additions & 0 deletions ISystemReward.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    pragma solidity 0.6.4;

    interface ISystemReward {
    function claimRewards(address payable to, uint256 amount) external;
    }
    21 changes: 21 additions & 0 deletions ITokenHub.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,21 @@
    pragma solidity 0.6.4;

    interface ITokenHub {

    function handleBindPackage(bytes calldata msgBytes, bytes calldata proof, uint64 height, uint64 packageSequence)
    external returns (bool);

    function handleTransferInPackage(bytes calldata msgBytes, bytes calldata proof, uint64 height, uint64 packageSequence)
    external returns (bool);

    function handleRefundPackage(bytes calldata msgBytes, bytes calldata proof, uint64 height, uint64 packageSequence)
    external returns (bool);

    function transferOut(address contractAddr, address recipient, uint256 amount, uint256 expireTime, uint256 relayFee)
    external payable returns (bool);

    /* solium-disable-next-line */
    function batchTransferOut(address[] calldata recipientAddrs, uint256[] calldata amounts, address[] calldata refundAddrs,
    address contractAddr, uint256 expireTime, uint256 relayFee) external payable returns (bool);

    }
    133 changes: 133 additions & 0 deletions Memory.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,133 @@
    pragma solidity 0.6.4;

    library Memory {

    // Size of a word, in bytes.
    uint internal constant WORD_SIZE = 32;
    // Size of the header of a 'bytes' array.
    uint internal constant BYTES_HEADER_SIZE = 32;
    // Address of the free memory pointer.
    uint internal constant FREE_MEM_PTR = 0x40;

    // Compares the 'len' bytes starting at address 'addr' in memory with the 'len'
    // bytes starting at 'addr2'.
    // Returns 'true' if the bytes are the same, otherwise 'false'.
    function equals(uint addr, uint addr2, uint len) internal pure returns (bool equal) {
    assembly {
    equal := eq(keccak256(addr, len), keccak256(addr2, len))
    }
    }

    // Compares the 'len' bytes starting at address 'addr' in memory with the bytes stored in
    // 'bts'. It is allowed to set 'len' to a lower value then 'bts.length', in which case only
    // the first 'len' bytes will be compared.
    // Requires that 'bts.length >= len'
    function equals(uint addr, uint len, bytes memory bts) internal pure returns (bool equal) {
    require(bts.length >= len);
    uint addr2;
    assembly {
    addr2 := add(bts, /*BYTES_HEADER_SIZE*/32)
    }
    return equals(addr, addr2, len);
    }

    // Allocates 'numBytes' bytes in memory. This will prevent the Solidity compiler
    // from using this area of memory. It will also initialize the area by setting
    // each byte to '0'.
    function allocate(uint numBytes) internal pure returns (uint addr) {
    // Take the current value of the free memory pointer, and update.
    assembly {
    addr := mload(/*FREE_MEM_PTR*/0x40)
    mstore(/*FREE_MEM_PTR*/0x40, add(addr, numBytes))
    }
    uint words = (numBytes + WORD_SIZE - 1) / WORD_SIZE;
    for (uint i = 0; i < words; i++) {
    assembly {
    mstore(add(addr, mul(i, /*WORD_SIZE*/32)), 0)
    }
    }
    }

    // Copy 'len' bytes from memory address 'src', to address 'dest'.
    // This function does not check the or destination, it only copies
    // the bytes.
    function copy(uint src, uint dest, uint len) internal pure {
    // Copy word-length chunks while possible
    for (; len >= WORD_SIZE; len -= WORD_SIZE) {
    assembly {
    mstore(dest, mload(src))
    }
    dest += WORD_SIZE;
    src += WORD_SIZE;
    }

    // Copy remaining bytes
    uint mask = 256 ** (WORD_SIZE - len) - 1;
    assembly {
    let srcpart := and(mload(src), not(mask))
    let destpart := and(mload(dest), mask)
    mstore(dest, or(destpart, srcpart))
    }
    }

    // Returns a memory pointer to the provided bytes array.
    function ptr(bytes memory bts) internal pure returns (uint addr) {
    assembly {
    addr := bts
    }
    }

    // Returns a memory pointer to the data portion of the provided bytes array.
    function dataPtr(bytes memory bts) internal pure returns (uint addr) {
    assembly {
    addr := add(bts, /*BYTES_HEADER_SIZE*/32)
    }
    }

    // This function does the same as 'dataPtr(bytes memory)', but will also return the
    // length of the provided bytes array.
    function fromBytes(bytes memory bts) internal pure returns (uint addr, uint len) {
    len = bts.length;
    assembly {
    addr := add(bts, /*BYTES_HEADER_SIZE*/32)
    }
    }

    // Creates a 'bytes memory' variable from the memory address 'addr', with the
    // length 'len'. The function will allocate new memory for the bytes array, and
    // the 'len bytes starting at 'addr' will be copied into that new memory.
    function toBytes(uint addr, uint len) internal pure returns (bytes memory bts) {
    bts = new bytes(len);
    uint btsptr;
    assembly {
    btsptr := add(bts, /*BYTES_HEADER_SIZE*/32)
    }
    copy(addr, btsptr, len);
    }

    // Get the word stored at memory address 'addr' as a 'uint'.
    function toUint(uint addr) internal pure returns (uint n) {
    assembly {
    n := mload(addr)
    }
    }

    // Get the word stored at memory address 'addr' as a 'bytes32'.
    function toBytes32(uint addr) internal pure returns (bytes32 bts) {
    assembly {
    bts := mload(addr)
    }
    }

    /*
    // Get the byte stored at memory address 'addr' as a 'byte'.
    function toByte(uint addr, uint8 index) internal pure returns (byte b) {
    require(index < WORD_SIZE);
    uint8 n;
    assembly {
    n := byte(index, mload(addr))
    }
    b = byte(n);
    }
    */
    }
    70 changes: 70 additions & 0 deletions MerkleProof.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,70 @@
    pragma solidity 0.6.4;

    import "Memory.sol";

    library MerkleProof {
    function validateMerkleProof(bytes32 appHash, string memory storeName, bytes memory key,
    bytes memory value, bytes memory proof) public view returns (bool) {
    if (appHash == bytes32(0)) {
    return false;
    }

    // | storeName | key length | key | value length | value | appHash | proof |
    // | 32 bytes | 32 bytes | | 32 bytes | | 32 bytes |
    bytes memory input = new bytes(128+key.length+value.length+proof.length);

    uint256 ptr = Memory.dataPtr(input);

    bytes memory storeNameBytes = bytes(storeName);
    assembly {
    mstore(add(ptr, 0), mload(add(storeNameBytes, 32)))
    }

    uint256 src;
    uint256 length;

    // write key length and key to input
    ptr+=32;
    (src, length) = Memory.fromBytes(key);
    assembly {
    mstore(ptr, length)
    }
    ptr+=32;
    Memory.copy(src, ptr, length);

    // write value length and value to input
    ptr+=length;
    (src, length) = Memory.fromBytes(value);
    assembly {
    mstore(ptr, length)
    }
    ptr+=32;
    Memory.copy(src, ptr, length);

    // write appHash to input
    ptr+=length;
    assembly {
    mstore(ptr, appHash)
    }

    // write proof to input
    ptr+=32;
    (src,length) = Memory.fromBytes(proof);
    Memory.copy(src, ptr, length);

    length = input.length+32;

    uint256[1] memory result;
    assembly {
    // call validateMerkleProof precompile contract
    // Contract address: 0x65
    if iszero(staticcall(not(0), 0x65, input, length, result, 0x20)) {}
    }

    if (result[0] != 0x01) {
    return false;
    }

    return true;
    }
    }
    77 changes: 77 additions & 0 deletions Ownable.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,77 @@
    pragma solidity 0.6.4;

    import "Context.sol";
    /**
    * @dev Contract module which provides a basic access control mechanism, where
    * there is an account (an owner) that can be granted exclusive access to
    * specific functions.
    *
    * This module is used through inheritance. It will make available the modifier
    * `onlyOwner`, which can be applied to your functions to restrict their use to
    * the owner.
    */
    contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
    * @dev Initializes the contract setting the deployer as the initial owner.
    */
    constructor () internal {
    address msgSender = _msgSender();
    _owner = msgSender;
    emit OwnershipTransferred(address(0), msgSender);
    }

    /**
    * @dev Returns the address of the current owner.
    */
    function owner() public view returns (address) {
    return _owner;
    }

    /**
    * @dev Throws if called by any account other than the owner.
    */
    modifier onlyOwner() {
    require(isOwner(), "Ownable: caller is not the owner");
    _;
    }

    /**
    * @dev Returns true if the caller is the current owner.
    */
    function isOwner() public view returns (bool) {
    return _msgSender() == _owner;
    }

    /**
    * @dev Leaves the contract without owner. It will not be possible to call
    * `onlyOwner` functions anymore. Can only be called by the current owner.
    *
    * NOTE: Renouncing ownership will leave the contract without an owner,
    * thereby removing any functionality that is only available to the owner.
    */
    function renounceOwnership() public onlyOwner {
    emit OwnershipTransferred(_owner, address(0));
    _owner = address(0);
    }

    /**
    * @dev Transfers ownership of the contract to a new account (`newOwner`).
    * Can only be called by the current owner.
    */
    function transferOwnership(address newOwner) public onlyOwner {
    _transferOwnership(newOwner);
    }

    /**
    * @dev Transfers ownership of the contract to a new account (`newOwner`).
    */
    function _transferOwnership(address newOwner) internal {
    require(newOwner != address(0), "Ownable: new owner is the zero address");
    emit OwnershipTransferred(_owner, newOwner);
    _owner = newOwner;
    }
    }
    140 changes: 140 additions & 0 deletions RelayerIncentivize.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,140 @@
    pragma solidity 0.6.4;

    import "IRelayerIncentivize.sol";


    contract RelayerIncentivize is IRelayerIncentivize {

    uint256 constant roundSize=1000;
    uint256 constant maximumWeight=400;

    mapping( uint256 => mapping(address => uint256) ) public _headerRelayersSubmitCount;
    mapping( uint256 => address payable[] ) public _headerRelayerAddressRecord;

    mapping( uint256 => mapping(address => uint256) ) public _transferRelayersSubmitCount;
    mapping( uint256 => address payable[] ) public _transferRelayerAddressRecord;

    mapping( uint256 => uint256) public _collectedRewardForHeaderRelayerPerRound;
    mapping( uint256 => uint256) public _collectedRewardForTransferRelayerPerRound;

    uint256 public _roundSequence = 0;
    uint256 public _countInRound=0;

    event LogRewardPeriodExpire(uint256 sequence, uint256 roundHeaderRelayerReward, uint256 roundTransferRelayerReward);

    function addReward(address payable headerRelayerAddr, address payable caller) external override payable returns (bool) {
    _countInRound++;

    uint256 reward = calculateRewardForHeaderRelayer(msg.value);
    _collectedRewardForHeaderRelayerPerRound[_roundSequence] += reward;
    _collectedRewardForTransferRelayerPerRound[_roundSequence] += msg.value - reward;

    if (_headerRelayersSubmitCount[_roundSequence][headerRelayerAddr]==0){
    _headerRelayerAddressRecord[_roundSequence].push(headerRelayerAddr);
    }
    _headerRelayersSubmitCount[_roundSequence][headerRelayerAddr]++;

    if (_transferRelayersSubmitCount[_roundSequence][caller]==0){
    _transferRelayerAddressRecord[_roundSequence].push(caller);
    }
    _transferRelayersSubmitCount[_roundSequence][caller]++;

    if (_countInRound==roundSize){
    emit LogRewardPeriodExpire(_roundSequence, _collectedRewardForHeaderRelayerPerRound[_roundSequence], _collectedRewardForTransferRelayerPerRound[_roundSequence]);

    claimHeaderRelayerReward(_roundSequence, caller);
    claimTransferRelayerReward(_roundSequence, caller);

    _roundSequence++;
    _countInRound = 0;
    }
    return true;
    }

    //TODO need further discussion
    function calculateRewardForHeaderRelayer(uint256 reward) internal pure returns (uint256) {
    return reward/5; //20%
    }

    function claimHeaderRelayerReward(uint256 sequence, address payable caller) internal returns (bool) {
    uint256 totalReward = _collectedRewardForHeaderRelayerPerRound[sequence];

    address payable[] memory relayers = _headerRelayerAddressRecord[sequence];
    uint256[] memory relayerWeight = new uint256[](relayers.length);
    for(uint256 index = 0; index < relayers.length; index++) {
    address relayer = relayers[index];
    uint256 weight = calculateHeaderRelayerWeight(_headerRelayersSubmitCount[sequence][relayer]);
    relayerWeight[index] = weight;
    }

    uint256 callerReward = totalReward * 5/100; //TODO need further discussion
    totalReward = totalReward - callerReward;
    uint256 remainReward = totalReward;
    for(uint256 index = 1; index < relayers.length; index++) {
    uint256 reward = relayerWeight[index]*totalReward/roundSize;
    relayers[index].transfer(reward);
    remainReward = remainReward-reward;
    }
    relayers[0].transfer(remainReward);
    caller.transfer(callerReward);

    delete _collectedRewardForHeaderRelayerPerRound[sequence];
    for (uint256 index = 0; index < relayers.length; index++){
    delete _headerRelayersSubmitCount[sequence][relayers[index]];
    }
    delete _headerRelayerAddressRecord[sequence];
    return true;
    }

    function claimTransferRelayerReward(uint256 sequence, address payable caller) internal returns (bool) {
    uint256 totalReward = _collectedRewardForTransferRelayerPerRound[sequence];

    address payable[] memory relayers = _transferRelayerAddressRecord[sequence];
    uint256[] memory relayerWeight = new uint256[](relayers.length);
    for(uint256 index = 0; index < relayers.length; index++) {
    address relayer = relayers[index];
    uint256 weight = calculateTransferRelayerWeight(_transferRelayersSubmitCount[sequence][relayer]);
    relayerWeight[index] = weight;
    }

    uint256 callerReward = totalReward * 5/100; //TODO need further discussion
    totalReward = totalReward - callerReward;
    uint256 remainReward = totalReward;
    for(uint256 index = 1; index < relayers.length; index++) {
    uint256 reward = relayerWeight[index]*totalReward/roundSize;
    relayers[index].transfer(reward);
    remainReward = remainReward-reward;
    }
    relayers[0].transfer(remainReward);
    caller.transfer(callerReward);

    delete _collectedRewardForTransferRelayerPerRound[sequence];
    for (uint256 index = 0; index < relayers.length; index++){
    delete _transferRelayersSubmitCount[sequence][relayers[index]];
    }
    delete _transferRelayerAddressRecord[sequence];
    return true;
    }

    function calculateTransferRelayerWeight(uint256 count) public pure returns(uint256) {
    if (count <= maximumWeight) {
    return count;
    } else if (maximumWeight < count && count <= 2*maximumWeight) {
    return maximumWeight;
    } else if (2*maximumWeight < count && count <= (2*maximumWeight + 3*maximumWeight/4 )) {
    return 3*maximumWeight - count;
    } else {
    return count/4;
    }
    }

    function calculateHeaderRelayerWeight(uint256 count) public pure returns(uint256) {
    if (count <= maximumWeight) {
    return count;
    } else if (maximumWeight < count && count <= 2*maximumWeight) {
    return maximumWeight;
    } else {
    return maximumWeight;
    }
    }
    }
    156 changes: 156 additions & 0 deletions SafeMath.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,156 @@
    pragma solidity 0.6.4;

    /**
    * @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.
    *
    * _Available since v2.4.0._
    */
    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.
    *
    * _Available since v2.4.0._
    */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    // Solidity only automatically asserts when dividing by 0
    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.
    *
    * _Available since v2.4.0._
    */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    require(b != 0, errorMessage);
    return a % b;
    }
    }
    246 changes: 246 additions & 0 deletions TendermintLightClient.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,246 @@
    pragma solidity 0.6.4;

    import "Memory.sol";
    import "ILightClient.sol";
    import "ISystemReward.sol";
    import "IRelayerHub.sol";

    contract TendermintLightClient is ILightClient {

    struct ConsensusState {
    uint64 preValidatorSetChangeHeight;
    bytes32 appHash;
    bytes32 curValidatorSetHash;
    bytes nextValidatorSet;
    }

    mapping(uint64 => ConsensusState) public _BBCLightClientConsensusState;
    mapping(uint64 => address payable) public _submitters;
    uint64 public _initialHeight;
    uint64 public _latestHeight;
    bool public _alreadyInit;


    address constant public _relayerHubContract=0x0000000000000000000000000000000000001006;
    address constant public _systemRewardContract=0x0000000000000000000000000000000000001002;

    string constant public _chainID="Binance-Chain-Nile";
    bytes constant public _initConsensusStateBytes = hex"746573742d636861696e00000000000000000000000000000000000000000000000000000000000229eca254b3859bffefaf85f4c95da9fbd26527766b784272789c30ec56b380b6eb96442aaab207bc59978ba3dd477690f5c5872334fc39e627723daa97e441e88ba4515150ec3182bc82593df36f8abb25a619187fcfab7e552b94e64ed2deed000000e8d4a51000";
    uint256 constant public _rewardForValidatorSetChange = 10000000000000000;

    event InitConsensusState(uint64 initHeight, bytes32 appHash);
    event SyncConsensusState(uint64 height, uint64 preValidatorSetChangeHeight, bytes32 appHash, bool validatorChanged);

    /* solium-disable-next-line */
    constructor() public {}


    modifier onlyRelayer() {
    require(IRelayerHub(_relayerHubContract).isRelayer(msg.sender), "the msg sender is not a relayer");
    _;
    }

    function init() public {
    require(!_alreadyInit, "already initialized");

    uint256 pointer;
    uint256 length;
    (pointer, length) = Memory.fromBytes(_initConsensusStateBytes);

    ConsensusState memory cs;
    uint64 height;
    (cs, height) = decodeConsensusState(pointer, length, false);
    cs.preValidatorSetChangeHeight = 0;
    _BBCLightClientConsensusState[height] = cs;

    _initialHeight = height;
    _latestHeight = height;
    _alreadyInit = true;

    emit InitConsensusState(_initialHeight, cs.appHash);
    }

    function syncTendermintHeader(bytes calldata header, uint64 height) external onlyRelayer returns (bool) {
    require(_submitters[height] == address(0x0), "can't sync duplicated header");
    require(height > _initialHeight, "can't sync header before _initialHeight");

    uint64 preValidatorSetChangeHeight = _latestHeight;
    ConsensusState memory cs = _BBCLightClientConsensusState[preValidatorSetChangeHeight];
    for(; preValidatorSetChangeHeight >= _initialHeight;) {
    if (preValidatorSetChangeHeight < height) {
    // find nearest previous height
    break;
    }
    preValidatorSetChangeHeight = cs.preValidatorSetChangeHeight;
    cs = _BBCLightClientConsensusState[preValidatorSetChangeHeight];
    }
    if (cs.nextValidatorSet.length == 0) {
    preValidatorSetChangeHeight = cs.preValidatorSetChangeHeight;
    cs.nextValidatorSet = _BBCLightClientConsensusState[preValidatorSetChangeHeight].nextValidatorSet;
    require(cs.nextValidatorSet.length != 0, "failed to load validator set data");
    }

    //32 + 32 + 8 + 32 + 32 + cs.nextValidatorSet.length;
    uint256 length = 136 + cs.nextValidatorSet.length;
    bytes memory input = new bytes(length+header.length);
    uint256 ptr = Memory.dataPtr(input);
    require(encodeConsensusState(cs, preValidatorSetChangeHeight, ptr, length), "failed to serialize consensus state");

    // write header to input
    uint256 src;
    ptr = ptr+length;
    (src, length) = Memory.fromBytes(header);
    Memory.copy(src, ptr, length);

    length = input.length+32;
    // Maximum validator quantity is 99
    bytes32[128] memory result;
    /* solium-disable-next-line */
    assembly {
    // call validateTendermintHeader precompile contract
    // Contract address: 0x64
    if iszero(staticcall(not(0), 0x64, input, length, result, 4096)) {
    revert(0, 0)
    }
    }

    //Judge if the validator set is changed
    /* solium-disable-next-line */
    assembly {
    length := mload(add(result, 0))
    }
    bool validatorChanged = false;
    if ((length&0x0100000000000000000000000000000000000000000000000000000000000000)!=0x00) {
    validatorChanged = true;
    ISystemReward(_systemRewardContract).claimRewards(msg.sender, _rewardForValidatorSetChange);//TODO further discussion about reward amount
    }
    length = length&0x000000000000000000000000000000000000000000000000ffffffffffffffff;

    /* solium-disable-next-line */
    assembly {
    ptr := add(result, 32)
    }

    uint64 actualHeaderHeight;
    (cs, actualHeaderHeight) = decodeConsensusState(ptr, length, !validatorChanged);
    require(actualHeaderHeight == height, "header height doesn't equal to the specified height");

    _submitters[height] = msg.sender;
    cs.preValidatorSetChangeHeight = preValidatorSetChangeHeight;
    _BBCLightClientConsensusState[height] = cs;
    if (height > _latestHeight) {
    _latestHeight = height;
    }

    emit SyncConsensusState(height, preValidatorSetChangeHeight, cs.appHash, validatorChanged);

    return true;
    }

    function isHeaderSynced(uint64 height) external override view returns (bool) {
    return _submitters[height] != address(0x0);
    }

    function getAppHash(uint64 height) external override view returns (bytes32) {
    return _BBCLightClientConsensusState[height].appHash;
    }

    function getSubmitter(uint64 height) external override view returns (address payable) {
    return _submitters[height];
    }

    // | length | _chainID | height | appHash | curValidatorSetHash | [{validator pubkey, voting power}] |
    // | 32 bytes | 32 bytes | 8 bytes | 32 bytes | 32 bytes | [{32 bytes, 8 bytes}] |
    /* solium-disable-next-line */
    function encodeConsensusState(ConsensusState memory cs, uint64 height, uint256 outputPtr, uint256 size) internal pure returns (bool) {
    uint256 validatorQuantity = cs.nextValidatorSet.length/40;

    outputPtr = outputPtr + size - 40 * validatorQuantity;

    uint256 src;
    uint256 length;
    (src, length) = Memory.fromBytes(cs.nextValidatorSet);
    Memory.copy(src, outputPtr, length);
    outputPtr = outputPtr-32;

    bytes32 hash = cs.curValidatorSetHash;
    /* solium-disable-next-line */
    assembly {
    mstore(outputPtr, hash)
    }
    outputPtr = outputPtr-32;

    hash = cs.appHash;
    /* solium-disable-next-line */
    assembly {
    mstore(outputPtr, hash)
    }
    outputPtr = outputPtr-32;

    /* solium-disable-next-line */
    assembly {
    mstore(outputPtr, height)
    }
    outputPtr = outputPtr-8;

    bytes memory chainIDBytes = bytes(_chainID);
    /* solium-disable-next-line */
    assembly {
    mstore(outputPtr, mload(add(chainIDBytes, 32)))
    }
    outputPtr = outputPtr-32;

    // size doesn't contain length
    size = size-32;
    /* solium-disable-next-line */
    assembly {
    mstore(outputPtr, size)
    }

    return true;
    }

    // | _chainID | height | appHash | curValidatorSetHash | [{validator pubkey, voting power}] |
    // | 32 bytes | 8 bytes | 32 bytes | 32 bytes | [{32 bytes, 8 bytes}] |
    /* solium-disable-next-line */
    function decodeConsensusState(uint256 ptr, uint256 size, bool leaveOutValidatorSet) internal pure returns(ConsensusState memory, uint64) {
    // 104 = 32 +32 +8 + 32 +32
    uint256 validatorSetLength = (size-104)/40;

    ptr = ptr+8;
    uint64 height;
    /* solium-disable-next-line */
    assembly {
    height := mload(ptr)
    }

    ptr = ptr+32;
    bytes32 appHash;
    /* solium-disable-next-line */
    assembly {
    appHash := mload(ptr)
    }

    ptr = ptr+32;
    bytes32 curValidatorSetHash;
    /* solium-disable-next-line */
    assembly {
    curValidatorSetHash := mload(ptr)
    }

    ConsensusState memory cs;
    cs.appHash = appHash;
    cs.curValidatorSetHash = curValidatorSetHash;

    if (!leaveOutValidatorSet) {
    uint256 dest;
    uint256 length;
    cs.nextValidatorSet = new bytes(40*validatorSetLength);
    (dest,length) = Memory.fromBytes(cs.nextValidatorSet);

    Memory.copy(ptr+32, dest, length);
    }

    return (cs, height);
    }
    }
    604 changes: 604 additions & 0 deletions TokenHub.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,604 @@
    pragma solidity 0.6.4;

    import "IERC20.sol";
    import "ILightClient.sol";
    import "IRelayerIncentivize.sol";
    import "MerkleProof.sol";
    import "ISystemReward.sol";
    import "ITokenHub.sol";
    import "IRelayerHub.sol";

    contract TokenHub is ITokenHub {

    struct BindPackage {
    bytes32 bep2TokenSymbol;
    address contractAddr;
    uint256 totalSupply;
    uint256 peggyAmount;
    uint8 erc20Decimals;
    uint64 expireTime;
    uint256 relayFee;
    }

    struct RefundPackage {
    uint256 refundAmount;
    address contractAddr;
    address payable refundAddr;
    uint16 reason;
    }

    struct TransferInPackage {
    bytes32 bep2TokenSymbol;
    address contractAddr;
    address refundAddr;
    address payable recipient;
    uint256 amount;
    uint64 expireTime;
    uint256 relayFee;
    }

    uint8 constant public bindChannelID = 0x01;
    uint8 constant public transferInChannelID = 0x02;
    uint8 constant public refundChannelID=0x03;
    // the store name of the package
    string constant public STORE_NAME = "ibc";
    uint256 constant public maxBep2TotalSupply = 9000000000000000000;

    bytes32 constant bep2TokenSymbolForBNB = 0x424E420000000000000000000000000000000000000000000000000000000000; // "BNB"
    bytes32 constant crossChainKeyPrefix = 0x0000000000000000000000000000000000000000000000000000000000010002; // last 5 bytes

    uint256 constant public _maxGasForCallingERC20=50000;

    uint256 constant public _minimumRelayFee=10000000000000000;
    uint256 constant public _refundRelayReward=10000000000000000;
    address constant public _relayerHubContract=0x0000000000000000000000000000000000001006;
    address constant public _systemRewardContract=0x0000000000000000000000000000000000001002;
    address constant public _lightClientContract=0x0000000000000000000000000000000000001003;
    address constant public _incentivizeContractForRelayers=0x0000000000000000000000000000000000001005;


    mapping(bytes32 => BindPackage) public _bindPackageRecord;
    mapping(address => bytes32) public _contractAddrToBEP2Symbol;
    mapping(address => uint256) public _erc20ContractDecimals;
    mapping(bytes32 => address) public _bep2SymbolToContractAddr;

    uint64 public _bindChannelSequence=0;
    uint64 public _transferInChannelSequence=0;
    uint64 public _refundChannelSequence=0;

    uint64 public _transferOutChannelSequence=0;
    uint64 public _bindResponseChannelSequence=0;
    uint64 public _transferInFailureChannelSequence=0;

    event LogBindRequest(address contractAddr, bytes32 bep2TokenSymbol, uint256 totalSupply, uint256 peggyAmount);
    event LogBindSuccess(uint256 sequence, address contractAddr, bytes32 bep2TokenSymbol, uint256 totalSupply, uint256 peggyAmount, uint256 decimals);
    event LogBindRejected(uint256 sequence, address contractAddr, bytes32 bep2TokenSymbol);
    event LogBindTimeout(uint256 sequence, address contractAddr, bytes32 bep2TokenSymbol);
    event LogBindInvalidParameter(uint256 sequence, address contractAddr, bytes32 bep2TokenSymbol);

    event LogTransferOut(uint256 sequence, address refundAddr, address recipient, uint256 amount, address contractAddr, bytes32 bep2TokenSymbol, uint256 expireTime, uint256 relayFee);
    event LogBatchTransferOut(uint256 sequence, uint256[] amounts, address contractAddr, bytes32 bep2TokenSymbol, uint256 expireTime, uint256 relayFee);
    event LogBatchTransferOutAddrs(uint256 sequence, address[] recipientAddrs, address[] refundAddrs);

    event LogTransferInSuccess(uint256 sequence, address recipient, uint256 amount, address contractAddr);
    event LogTransferInFailureTimeout(uint256 sequence, address refundAddr, address recipient, uint256 bep2TokenAmount, address contractAddr, bytes32 bep2TokenSymbol, uint256 expireTime);
    event LogTransferInFailureInsufficientBalance(uint256 sequence, address refundAddr, address recipient, uint256 bep2TokenAmount, address contractAddr, bytes32 bep2TokenSymbol, uint256 actualBalance);
    event LogTransferInFailureUnboundToken(uint256 sequence, address refundAddr, address recipient, uint256 bep2TokenAmount, address contractAddr, bytes32 bep2TokenSymbol);
    event LogTransferInFailureUnknownReason(uint256 sequence, address refundAddr, address recipient, uint256 bep2TokenAmount, address contractAddr, bytes32 bep2TokenSymbol);

    event LogRefundSuccess(address contractAddr, address refundAddr, uint256 amount, uint16 reason);
    event LogRefundFailureInsufficientBalance(address contractAddr, address refundAddr, uint256 amount, uint16 reason, uint256 actualBalance);
    event LogRefundFailureUnboundToken(address contractAddr, address refundAddr, uint256 amount, uint16 reason);
    event LogRefundFailureUnknownReason(address contractAddr, address refundAddr, uint256 amount, uint16 reason);

    event LogUnexpectedRevertInERC20(address contractAddr, string reason);
    event LogUnexpectedFailureAssertionInERC20(address contractAddr, bytes lowLevelData);

    constructor() public {}



    modifier onlyHeaderSynced(uint64 height) {
    require(ILightClient(_lightClientContract).isHeaderSynced(height), "reference header is not synced");
    _;
    }


    modifier onlyRelayer() {
    require(IRelayerHub(_relayerHubContract).isRelayer(msg.sender), "the msg sender is not a relayer");
    _;
    }

    function bep2TokenSymbolConvert(string memory symbol) public pure returns(bytes32) {
    bytes32 result;
    assembly {
    result := mload(add(symbol, 32))
    }
    return result;
    }

    // | length | prefix | sourceChainID| destinationChainID | channelID | sequence |
    // | 32 bytes | 1 byte | 2 bytes | 2 bytes | 1 bytes | 8 bytes |
    function generateKey(uint8 channelID, uint256 sequence) internal pure returns(bytes memory) {
    bytes memory key = new bytes(14);

    uint256 ptr;
    assembly {
    ptr := add(key, 14)
    }


    assembly {
    mstore(ptr, sequence)
    }
    ptr -= 8;


    assembly {
    mstore(ptr, channelID)
    }
    ptr -= 1;

    assembly {
    mstore(ptr, crossChainKeyPrefix)
    }
    ptr -= 5;

    assembly {
    mstore(ptr, 14)
    }

    return key;
    }

    // | length | bep2TokenSymbol | contractAddr | totalSupply | peggyAmount | decimals | expireTime | relayFee |
    // | 32 bytes | 32 bytes | 20 bytes | 32 bytes | 32 bytes | 1 byte | 8 bytes | 32 bytes |
    function decodeBindPackage(bytes memory value) internal pure returns(BindPackage memory) {
    BindPackage memory bindPackage;

    uint256 ptr;
    assembly {
    ptr := value
    }

    bytes32 bep2TokenSymbol;
    ptr+=32;
    assembly {
    bep2TokenSymbol := mload(ptr)
    }
    bindPackage.bep2TokenSymbol = bep2TokenSymbol;

    address addr;

    ptr+=20;
    assembly {
    addr := mload(ptr)
    }
    bindPackage.contractAddr = addr;

    uint256 tempValue;
    ptr+=32;
    assembly {
    tempValue := mload(ptr)
    }
    bindPackage.totalSupply = tempValue;

    ptr+=32;
    assembly {
    tempValue := mload(ptr)
    }
    bindPackage.peggyAmount = tempValue;

    ptr+=1;
    uint8 decimals;
    assembly {
    decimals := mload(ptr)
    }
    bindPackage.erc20Decimals = decimals;

    ptr+=8;
    uint64 expireTime;
    assembly {
    expireTime := mload(ptr)
    }
    bindPackage.expireTime = expireTime;

    ptr+=32;
    assembly {
    tempValue := mload(ptr)
    }
    bindPackage.relayFee = tempValue;

    return bindPackage;
    }

    function handleBindPackage(bytes calldata msgBytes, bytes calldata proof, uint64 height, uint64 packageSequence) onlyHeaderSynced(height) onlyRelayer override external returns (bool) {
    require(packageSequence==_bindChannelSequence, "wrong bind sequence");
    require(msgBytes.length==157, "wrong bind package size");
    bytes32 appHash = ILightClient(_lightClientContract).getAppHash(height);
    require(MerkleProof.validateMerkleProof(appHash, STORE_NAME, generateKey(bindChannelID, _bindChannelSequence), msgBytes, proof), "invalid merkle proof");
    _bindChannelSequence++;

    address payable tendermintHeaderSubmitter = ILightClient(_lightClientContract).getSubmitter(height);
    BindPackage memory bindPackage = decodeBindPackage(msgBytes);
    IRelayerIncentivize(_incentivizeContractForRelayers).addReward{value: bindPackage.relayFee}(tendermintHeaderSubmitter, msg.sender);

    _bindPackageRecord[bindPackage.bep2TokenSymbol]=bindPackage;
    emit LogBindRequest(bindPackage.contractAddr, bindPackage.bep2TokenSymbol, bindPackage.totalSupply, bindPackage.peggyAmount);
    return true;
    }

    function checkSymbol(string memory erc20Symbol, bytes32 bep2TokenSymbol) public pure returns(bool) {
    bytes memory erc20SymbolBytes = bytes(erc20Symbol);
    //Upper case string
    for (uint i = 0; i < erc20SymbolBytes.length; i++) {
    if (0x61 <= uint8(erc20SymbolBytes[i]) && uint8(erc20SymbolBytes[i]) <= 0x7A) {
    erc20SymbolBytes[i] = byte(uint8(erc20SymbolBytes[i]) - 0x20);
    }
    }

    bytes memory bep2TokenSymbolBytes = new bytes(32);
    assembly {
    mstore(add(bep2TokenSymbolBytes, 32), bep2TokenSymbol)
    }
    bool symbolMatch = true;
    for(uint256 index=0; index < erc20SymbolBytes.length; index++) {
    if (erc20SymbolBytes[index] != bep2TokenSymbolBytes[index]) {
    symbolMatch = false;
    break;
    }
    }
    return symbolMatch;
    }

    function approveBind(address contractAddr, string memory bep2Symbol) public returns (bool) {
    bytes32 bep2TokenSymbol = bep2TokenSymbolConvert(bep2Symbol);
    BindPackage memory bindPackage = _bindPackageRecord[bep2TokenSymbol];
    uint256 lockedAmount = bindPackage.totalSupply-bindPackage.peggyAmount;
    require(contractAddr==bindPackage.contractAddr, "contact address doesn't equal to the contract address in bind request");
    require(IERC20(contractAddr).getOwner()==msg.sender, "only erc20 owner can approve this bind request");
    require(IERC20(contractAddr).allowance(msg.sender, address(this))==lockedAmount, "allowance doesn't equal to (totalSupply - peggyAmount)");

    if (bindPackage.expireTime<block.timestamp) {
    emit LogBindTimeout(_bindResponseChannelSequence++, bindPackage.contractAddr, bindPackage.bep2TokenSymbol);
    delete _bindPackageRecord[bep2TokenSymbol];
    return false;
    }

    uint256 decimals = IERC20(contractAddr).decimals();
    string memory erc20Symbol = IERC20(contractAddr).symbol();
    if (!checkSymbol(erc20Symbol, bep2TokenSymbol) ||
    _bep2SymbolToContractAddr[bindPackage.bep2TokenSymbol]!=address(0x00)||
    _contractAddrToBEP2Symbol[bindPackage.contractAddr]!=bytes32(0x00)||
    IERC20(bindPackage.contractAddr).totalSupply()!=bindPackage.totalSupply||
    decimals!=bindPackage.erc20Decimals) {
    delete _bindPackageRecord[bep2TokenSymbol];
    emit LogBindInvalidParameter(_bindResponseChannelSequence++, bindPackage.contractAddr, bindPackage.bep2TokenSymbol);
    return false;
    }
    IERC20(contractAddr).transferFrom(msg.sender, address(this), lockedAmount);
    _contractAddrToBEP2Symbol[bindPackage.contractAddr] = bindPackage.bep2TokenSymbol;
    _erc20ContractDecimals[bindPackage.contractAddr] = bindPackage.erc20Decimals;
    _bep2SymbolToContractAddr[bindPackage.bep2TokenSymbol] = bindPackage.contractAddr;

    delete _bindPackageRecord[bep2TokenSymbol];
    emit LogBindSuccess(_bindResponseChannelSequence++, bindPackage.contractAddr, bindPackage.bep2TokenSymbol, bindPackage.totalSupply, bindPackage.peggyAmount, decimals);
    return true;
    }

    function rejectBind(address contractAddr, string memory bep2Symbol) public returns (bool) {
    bytes32 bep2TokenSymbol = bep2TokenSymbolConvert(bep2Symbol);
    BindPackage memory bindPackage = _bindPackageRecord[bep2TokenSymbol];
    require(contractAddr==bindPackage.contractAddr, "contact address doesn't equal to the contract address in bind request");
    require(IERC20(contractAddr).getOwner()==msg.sender, "only erc20 owner can reject");
    delete _bindPackageRecord[bep2TokenSymbol];
    emit LogBindRejected(_bindResponseChannelSequence++, bindPackage.contractAddr, bindPackage.bep2TokenSymbol);
    return true;
    }

    function expireBind(string memory bep2Symbol) public returns (bool) {
    bytes32 bep2TokenSymbol = bep2TokenSymbolConvert(bep2Symbol);
    BindPackage memory bindPackage = _bindPackageRecord[bep2TokenSymbol];
    require(bindPackage.expireTime<block.timestamp, "bind request is not expired");
    delete _bindPackageRecord[bep2TokenSymbol];
    emit LogBindTimeout(_bindResponseChannelSequence++, bindPackage.contractAddr, bindPackage.bep2TokenSymbol);
    return true;
    }

    // | length | bep2TokenSymbol | contractAddr | sender | recipient | amount | expireTime | relayFee |
    // | 32 bytes | 32 bytes | 20 bytes | 20 bytes | 20 bytes | 32 bytes | 8 bytes | 32 bytes |
    function decodeTransferInPackage(bytes memory value) internal pure returns (TransferInPackage memory) {
    TransferInPackage memory transferInPackage;

    uint256 ptr;
    assembly {
    ptr := value
    }

    uint256 tempValue;
    address payable recipient;
    address addr;

    ptr+=32;
    bytes32 bep2TokenSymbol;
    assembly {
    bep2TokenSymbol := mload(ptr)
    }
    transferInPackage.bep2TokenSymbol = bep2TokenSymbol;

    ptr+=20;
    assembly {
    addr := mload(ptr)
    }
    transferInPackage.contractAddr = addr;

    ptr+=20;
    assembly {
    addr := mload(ptr)
    }
    transferInPackage.refundAddr = addr;

    ptr+=20;
    assembly {
    recipient := mload(ptr)
    }
    transferInPackage.recipient = recipient;

    ptr+=32;
    assembly {
    tempValue := mload(ptr)
    }
    transferInPackage.amount = tempValue;

    ptr+=8;
    uint64 expireTime;
    assembly {
    expireTime := mload(ptr)
    }
    transferInPackage.expireTime = expireTime;

    ptr+=32;
    assembly {
    tempValue := mload(ptr)
    }
    transferInPackage.relayFee = tempValue;

    return transferInPackage;
    }

    function handleTransferInPackage(bytes calldata msgBytes, bytes calldata proof, uint64 height, uint64 packageSequence) onlyHeaderSynced(height) onlyRelayer override external returns (bool) {
    require(packageSequence==_transferInChannelSequence, "wrong transfer sequence");
    require(msgBytes.length==164, "wrong transfer package size");
    bytes32 appHash = ILightClient(_lightClientContract).getAppHash(height);
    require(MerkleProof.validateMerkleProof(appHash, STORE_NAME, generateKey(transferInChannelID, _transferInChannelSequence), msgBytes, proof), "invalid merkle proof");
    _transferInChannelSequence++;

    address payable tendermintHeaderSubmitter = ILightClient(_lightClientContract).getSubmitter(height);
    TransferInPackage memory transferInPackage = decodeTransferInPackage(msgBytes);
    IRelayerIncentivize(_incentivizeContractForRelayers).addReward{value: transferInPackage.relayFee}(tendermintHeaderSubmitter, msg.sender);

    if (transferInPackage.contractAddr==address(0x0) && transferInPackage.bep2TokenSymbol==bep2TokenSymbolForBNB) {
    if (block.timestamp > transferInPackage.expireTime) {
    emit LogTransferInFailureTimeout(_transferInFailureChannelSequence++, transferInPackage.refundAddr, transferInPackage.recipient, transferInPackage.amount/10**10, transferInPackage.contractAddr, transferInPackage.bep2TokenSymbol, transferInPackage.expireTime);
    return false;
    }
    if (address(this).balance < transferInPackage.amount) {
    emit LogTransferInFailureInsufficientBalance(_transferInFailureChannelSequence++, transferInPackage.refundAddr, transferInPackage.recipient, transferInPackage.amount/10**10, transferInPackage.contractAddr, transferInPackage.bep2TokenSymbol, address(this).balance);
    return false;
    }
    if (!transferInPackage.recipient.send(transferInPackage.amount)) {
    emit LogTransferInFailureUnknownReason(_transferInFailureChannelSequence++, transferInPackage.refundAddr, transferInPackage.recipient, transferInPackage.amount/10**10, transferInPackage.contractAddr, transferInPackage.bep2TokenSymbol);
    return false;
    }
    emit LogTransferInSuccess(_transferInChannelSequence-1, transferInPackage.recipient, transferInPackage.amount, transferInPackage.contractAddr);
    return true;
    } else {
    uint256 bep2Amount = transferInPackage.amount * (10**8) / (10**_erc20ContractDecimals[transferInPackage.contractAddr]);
    if (_contractAddrToBEP2Symbol[transferInPackage.contractAddr]!= transferInPackage.bep2TokenSymbol) {
    emit LogTransferInFailureUnboundToken(_transferInFailureChannelSequence++, transferInPackage.refundAddr, transferInPackage.recipient, bep2Amount, transferInPackage.contractAddr, transferInPackage.bep2TokenSymbol);
    return false;
    }
    if (block.timestamp > transferInPackage.expireTime) {
    emit LogTransferInFailureTimeout(_transferInFailureChannelSequence++, transferInPackage.refundAddr, transferInPackage.recipient, bep2Amount, transferInPackage.contractAddr, transferInPackage.bep2TokenSymbol, transferInPackage.expireTime);
    return false;
    }
    try IERC20(transferInPackage.contractAddr).transfer{gas: _maxGasForCallingERC20}(transferInPackage.recipient, transferInPackage.amount) returns (bool success) {
    if (success) {
    emit LogTransferInSuccess(_transferInChannelSequence-1, transferInPackage.recipient, transferInPackage.amount, transferInPackage.contractAddr);
    return true;
    } else {
    try IERC20(transferInPackage.contractAddr).balanceOf{gas: _maxGasForCallingERC20}(address(this)) returns (uint256 actualBalance) {
    emit LogTransferInFailureInsufficientBalance(_transferInFailureChannelSequence++, transferInPackage.refundAddr, transferInPackage.recipient, bep2Amount, transferInPackage.contractAddr, transferInPackage.bep2TokenSymbol, actualBalance);
    return false;
    } catch Error(string memory reason) {
    emit LogTransferInFailureUnknownReason(_transferInFailureChannelSequence++, transferInPackage.refundAddr, transferInPackage.recipient, bep2Amount, transferInPackage.contractAddr, transferInPackage.bep2TokenSymbol);
    emit LogUnexpectedRevertInERC20(transferInPackage.contractAddr, reason);
    return false;
    } catch (bytes memory lowLevelData) {
    emit LogTransferInFailureUnknownReason(_transferInFailureChannelSequence++, transferInPackage.refundAddr, transferInPackage.recipient, bep2Amount, transferInPackage.contractAddr, transferInPackage.bep2TokenSymbol);
    emit LogUnexpectedFailureAssertionInERC20(transferInPackage.contractAddr, lowLevelData);
    return false;
    }
    }
    } catch Error(string memory reason) {
    emit LogTransferInFailureUnknownReason(_transferInFailureChannelSequence++, transferInPackage.refundAddr, transferInPackage.recipient, bep2Amount, transferInPackage.contractAddr, transferInPackage.bep2TokenSymbol);
    emit LogUnexpectedRevertInERC20(transferInPackage.contractAddr, reason);
    return false;
    } catch (bytes memory lowLevelData) {
    emit LogTransferInFailureUnknownReason(_transferInFailureChannelSequence++, transferInPackage.refundAddr, transferInPackage.recipient, bep2Amount, transferInPackage.contractAddr, transferInPackage.bep2TokenSymbol);
    emit LogUnexpectedFailureAssertionInERC20(transferInPackage.contractAddr, lowLevelData);
    return false;
    }
    }
    }

    // | length | refundAmount | contractAddr | refundAddr | failureReason |
    // | 32 bytes | 32 bytes | 20 bytes | 20 bytes | 2 bytes |
    function decodeRefundPackage(bytes memory value) internal pure returns(RefundPackage memory) {
    RefundPackage memory refundPackage;

    uint256 ptr;
    assembly {
    ptr := value
    }

    ptr+=32;
    uint256 refundAmount;
    assembly {
    refundAmount := mload(ptr)
    }
    refundPackage.refundAmount = refundAmount;

    ptr+=20;
    address contractAddr;
    assembly {
    contractAddr := mload(ptr)
    }
    refundPackage.contractAddr = contractAddr;

    ptr+=20;
    address payable refundAddr;
    assembly {
    refundAddr := mload(ptr)
    }
    refundPackage.refundAddr = refundAddr;

    ptr+=2;
    uint16 reason;
    assembly {
    reason := mload(ptr)
    }
    refundPackage.reason = reason;


    return refundPackage;
    }

    function handleRefundPackage(bytes calldata msgBytes, bytes calldata proof, uint64 height, uint64 packageSequence) onlyHeaderSynced(height) onlyRelayer override external returns (bool) {
    require(packageSequence==_refundChannelSequence, "wrong refund sequence");
    require(msgBytes.length==74, "wrong refund package size");
    bytes32 appHash = ILightClient(_lightClientContract).getAppHash(height);
    require(MerkleProof.validateMerkleProof(appHash, STORE_NAME, generateKey(refundChannelID, _refundChannelSequence), msgBytes, proof), "invalid merkle proof");
    _refundChannelSequence++;

    address payable tendermintHeaderSubmitter = ILightClient(_lightClientContract).getSubmitter(height);
    //TODO system reward, need further discussion,
    //TODO taking malicious refund cases caused by inconsistent total supply into consideration, so this reward must be less than minimum relay fee
    uint256 reward = _refundRelayReward / 5;
    ISystemReward(_systemRewardContract).claimRewards(tendermintHeaderSubmitter, reward);
    reward = _refundRelayReward-reward;
    ISystemReward(_systemRewardContract).claimRewards(msg.sender, reward);

    RefundPackage memory refundPackage = decodeRefundPackage(msgBytes);
    if (refundPackage.contractAddr==address(0x0)) {
    uint256 actualBalance = address(this).balance;
    if (actualBalance < refundPackage.refundAmount) {
    emit LogRefundFailureInsufficientBalance(refundPackage.contractAddr, refundPackage.refundAddr, refundPackage.refundAmount, refundPackage.reason, actualBalance);
    return false;
    }
    if (!refundPackage.refundAddr.send(refundPackage.refundAmount)){
    emit LogRefundFailureUnknownReason(refundPackage.contractAddr, refundPackage.refundAddr, refundPackage.refundAmount, refundPackage.reason);
    return false;
    }
    emit LogRefundSuccess(refundPackage.contractAddr, refundPackage.refundAddr, refundPackage.refundAmount, refundPackage.reason);
    return true;
    } else {
    if (_contractAddrToBEP2Symbol[refundPackage.contractAddr]==bytes32(0x00)) {
    emit LogRefundFailureUnboundToken(refundPackage.contractAddr, refundPackage.refundAddr, refundPackage.refundAmount, refundPackage.reason);
    return false;
    }
    try IERC20(refundPackage.contractAddr).transfer{gas: _maxGasForCallingERC20}(refundPackage.refundAddr, refundPackage.refundAmount) returns (bool success) {
    if (success) {
    emit LogRefundSuccess(refundPackage.contractAddr, refundPackage.refundAddr, refundPackage.refundAmount, refundPackage.reason);
    return true;
    } else {
    try IERC20(refundPackage.contractAddr).balanceOf{gas: _maxGasForCallingERC20}(address(this)) returns (uint256 actualBalance) {
    emit LogRefundFailureInsufficientBalance(refundPackage.contractAddr, refundPackage.refundAddr, refundPackage.refundAmount, refundPackage.reason, actualBalance);
    return false;
    } catch Error(string memory reason) {
    emit LogRefundFailureUnknownReason(refundPackage.contractAddr, refundPackage.refundAddr, refundPackage.refundAmount, refundPackage.reason);
    emit LogUnexpectedRevertInERC20(refundPackage.contractAddr, reason);
    return false;
    } catch (bytes memory lowLevelData) {
    emit LogRefundFailureUnknownReason(refundPackage.contractAddr, refundPackage.refundAddr, refundPackage.refundAmount, refundPackage.reason);
    emit LogUnexpectedFailureAssertionInERC20(refundPackage.contractAddr, lowLevelData);
    return false;
    }
    }
    } catch Error(string memory reason) {
    emit LogRefundFailureUnknownReason(refundPackage.contractAddr, refundPackage.refundAddr, refundPackage.refundAmount, refundPackage.reason);
    emit LogUnexpectedRevertInERC20(refundPackage.contractAddr, reason);
    return false;
    } catch (bytes memory lowLevelData) {
    emit LogRefundFailureUnknownReason(refundPackage.contractAddr, refundPackage.refundAddr, refundPackage.refundAmount, refundPackage.reason);
    emit LogUnexpectedFailureAssertionInERC20(refundPackage.contractAddr, lowLevelData);
    return false;
    }
    }
    }

    function transferOut(address contractAddr, address recipient, uint256 amount, uint256 expireTime, uint256 relayFee) override external payable returns (bool) {
    require(relayFee%(10**10)==0, "relayFee is must be N*10^10");
    require(relayFee>=_minimumRelayFee, "relayFee is too little");
    require(expireTime>=block.timestamp + 120, "expireTime must be two minutes later");
    uint256 convertedRelayFee = relayFee / (10**10); // native bnb decimals is 8 on BBC, while the native bnb decimals on BSC is 18
    bytes32 bep2TokenSymbol;
    uint256 convertedAmount;
    if (contractAddr==address(0x0)) {
    require(msg.value==amount+relayFee, "received BNB amount doesn't equal to the sum of transfer amount and relayFee");
    convertedAmount = amount / (10**10); // native bnb decimals is 8 on BBC, while the native bnb decimals on BSC is 18
    bep2TokenSymbol=bep2TokenSymbolForBNB;
    } else {
    uint256 erc20TokenDecimals=_erc20ContractDecimals[contractAddr];
    if (erc20TokenDecimals > 8) {
    uint256 extraPrecision = 10**(erc20TokenDecimals-8);
    require(amount%extraPrecision==0, "invalid transfer amount: precision loss in amount conversion");
    }
    bep2TokenSymbol = _contractAddrToBEP2Symbol[contractAddr];
    require(bep2TokenSymbol!=bytes32(0x00), "the contract has not been bind to any bep2 token");
    require(msg.value==relayFee, "received BNB amount doesn't equal to relayFee");
    require(IERC20(contractAddr).transferFrom(msg.sender, address(this), amount));
    convertedAmount = amount * (10**8)/ (10**erc20TokenDecimals); // bep2 token decimals is 8 on BBC
    require(convertedAmount<=maxBep2TotalSupply, "amount is too large, int64 overflow");
    }
    emit LogTransferOut(_transferOutChannelSequence++, msg.sender, recipient, convertedAmount, contractAddr, bep2TokenSymbol, expireTime, convertedRelayFee);
    return true;
    }

    function batchTransferOut(address[] calldata recipientAddrs, uint256[] calldata amounts, address[] calldata refundAddrs, address contractAddr, uint256 expireTime, uint256 relayFee) override external payable returns (bool) {
    require(recipientAddrs.length == amounts.length, "Length of recipientAddrs doesn't equal to length of amounts");
    require(recipientAddrs.length == refundAddrs.length, "Length of recipientAddrs doesn't equal to length of refundAddrs");
    require(relayFee/amounts.length>=_minimumRelayFee, "relayFee is too little");
    require(relayFee%(10**10)==0, "relayFee must be N*10^10");
    require(expireTime>=block.timestamp + 120, "expireTime must be two minutes later");
    uint256 totalAmount = 0;
    for (uint i = 0; i < amounts.length; i++) {
    totalAmount += amounts[i];
    }
    uint256[] memory convertedAmounts = new uint256[](amounts.length);
    bytes32 bep2TokenSymbol;
    if (contractAddr==address(0x0)) {
    for (uint8 i = 0; i < amounts.length; i++) {
    require(amounts[i]%10**10==0, "invalid transfer amount");
    convertedAmounts[i] = amounts[i]/10**10;
    }
    require(msg.value==totalAmount+relayFee, "received BNB amount doesn't equal to the sum of transfer amount and relayFee");
    bep2TokenSymbol=bep2TokenSymbolForBNB;
    } else {
    uint256 erc20TokenDecimals=_erc20ContractDecimals[contractAddr];
    for (uint i = 0; i < amounts.length; i++) {
    require((amounts[i]*(10**8)%(10**erc20TokenDecimals))==0, "invalid transfer amount");
    uint256 convertedAmount = amounts[i]*(10**8)/(10**erc20TokenDecimals);
    require(convertedAmount<=maxBep2TotalSupply, "amount is too large, int64 overflow");
    convertedAmounts[i] = convertedAmount;
    }
    bep2TokenSymbol = _contractAddrToBEP2Symbol[contractAddr];
    require(bep2TokenSymbol!=bytes32(0x00), "the contract has not been bind to any bep2 token");
    require(msg.value==relayFee, "received BNB amount doesn't equal to relayFee");
    require(IERC20(contractAddr).transferFrom(msg.sender, address(this), totalAmount));
    }
    emit LogBatchTransferOut(_transferOutChannelSequence, convertedAmounts, contractAddr, bep2TokenSymbol, expireTime, relayFee/(10**10));
    emit LogBatchTransferOutAddrs(_transferOutChannelSequence++, recipientAddrs, refundAddrs);
    return true;
    }
    }
    267 changes: 267 additions & 0 deletions XYZToken.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,267 @@
    pragma solidity 0.6.4;

    import "Context.sol";
    import "IERC20.sol";
    import "SafeMath.sol";
    import "Ownable.sol";

    /**
    * @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 {ERC20Mintable}.
    *
    * 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 XYZToken is Context, IERC20, Ownable {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;
    uint8 public _decimals;
    string public _symbol;
    string public _name;
    address private _bep2TokenOwner;

    constructor() public {
    _name = "XYZ token";
    _symbol = "xyz";
    _decimals = 8;
    _totalSupply = 10000000000000000;
    _balances[msg.sender] = _totalSupply;
    _bep2TokenOwner=address(0x35D9D41a13D6c2e01c9b1e242BAf2dF98e7E8c48);

    emit Transfer(address(0), msg.sender, _totalSupply);
    }

    /**
    * @dev Returns the address of the current owner.
    */
    function getOwner() override public view returns (address) {
    return owner();
    }

    /**
    * @dev Returns the token decimals.
    */
    function decimals() override external view returns (uint256) {
    return _decimals;
    }

    /**
    * @dev Returns the contract owner.
    */
    function symbol() override external view returns (string memory) {
    return _symbol;
    }

    /**
    * @dev See {IERC20-totalSupply}.
    */
    function totalSupply() override public view returns (uint256) {
    return _totalSupply;
    }

    /**
    * @dev See {IERC20-balanceOf}.
    */
    function balanceOf(address account) override public view 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) override public returns (bool) {
    _transfer(_msgSender(), recipient, amount);
    return true;
    }

    /**
    * @dev See {IERC20-allowance}.
    */
    function allowance(address owner, address spender) override public view returns (uint256) {
    return _allowances[owner][spender];
    }

    /**
    * @dev See {IERC20-approve}.
    *
    * Requirements:
    *
    * - `spender` cannot be the zero address.
    */
    function approve(address spender, uint256 amount) override public returns (bool) {
    _approve(_msgSender(), 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) override public returns (bool) {
    _transfer(sender, recipient, amount);
    _approve(sender, _msgSender(), _allowances[sender][_msgSender()].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 returns (bool) {
    _approve(_msgSender(), spender, _allowances[_msgSender()][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 returns (bool) {
    _approve(_msgSender(), spender, _allowances[_msgSender()][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 {
    require(sender != address(0), "ERC20: transfer from the zero address");
    require(recipient != address(0), "ERC20: transfer to the zero address");

    _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 {
    require(account != address(0), "ERC20: mint to the zero address");

    _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 {
    require(account != address(0), "ERC20: burn from the zero address");

    _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 is 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 {
    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);
    }

    /**
    * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
    * from the caller's allowance.
    *
    * See {_burn} and {_approve}.
    */
    function _burnFrom(address account, uint256 amount) internal {
    _burn(account, amount);
    _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
    }
    }