diff --git a/.github/workflows/ci-bridge.yml b/.github/workflows/ci-bridge.yml index 98b7e76..f42eba4 100644 --- a/.github/workflows/ci-bridge.yml +++ b/.github/workflows/ci-bridge.yml @@ -1,4 +1,4 @@ -name: "CI for Product Bridge" +name: "CI for Product Bridge - synthetic change for testing" on: push: {} @@ -27,5 +27,5 @@ jobs: version: nightly - name: Run Foundry tests - run: forge test + run: forge test --disable-block-gas-limit working-directory: smart-contracts diff --git a/.gitignore b/.gitignore index 23d4a5e..2185bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ Debug/ .trunk/** !.trunk/trunk.yaml +**/.env diff --git a/docs/zilbridge.md b/docs/zilbridge.md new file mode 100644 index 0000000..36919bd --- /dev/null +++ b/docs/zilbridge.md @@ -0,0 +1,27 @@ +# Zilbridge/XBridge integration + + +## CrossChainManager extensions + +The currently deployed CCM on Ethereum does not contain functions to register lockProxy extensions. + +These can be run remotely from the `counterpartChainId` (in `lockProxy`), currently set to 5 (which is presumably Carbon). + +This means we need to either proxy or replace it. + +Replacing it is undesirable, because the address of the CCM is baked into the configuration files for the relayers. + +My first attempt was to proxy it with a `CCMExtendProxy`, but this doesn't work, because: + + * To upgrade you have to call `ccmProxy::upgradeEthCrossChainManager()` + * (side-note: this calls the _current_ `eccm.upgradeToNew()` which hands ownership of the CCM data (proxied by the CCM contract) to the CCMExtendProxty, which now needs to hand it back to the old `ccm`) + * Subsequent calls through the `CCMExtendProxy` need to use the original `ccm` state, and therefore have the `CCMExtendProxy` as `msg.sender`. + * But there is no way to make the `CCMExtendProxy` an owner of the `ccm`. + +The second attempt is to write a new CCM contract which duplicates the +original CCM and contains the new functions. Sadly, this means that +someone needs to remember what the whitelist parameters were, because +it is a map that does not emit events and we thus can't work out what +is in it. + + diff --git a/smart-contracts/README.md b/smart-contracts/README.md index 4f65145..2233f96 100644 --- a/smart-contracts/README.md +++ b/smart-contracts/README.md @@ -134,6 +134,22 @@ Currently periphery only features ERC20 bridge that is `LockAndRelease` on one e To deploy the `TokenManagers` on new chains the `LockAndReleaseTokenManagerUpgradeableV3` and `MintAndBurnTokenManagerUpgradeableV3` on each end of the chain if not already there. The initializer function needs to be added to these functions to initialize the owner and setting the gateways necessary. Then a new deployment script can be created and used accordingly. +### ZilBridge + +`zilbridge` contains contracts used to assume the functionality of +ZilBridge. The tests for zilbridge are mostly over in the parallel +`zilbridge-hardhat` directory, because a lot of them depend on Scilla +contracts and forge does not yet support Scilla. + +`zilbridge/1` are taken from: + + - [lockproxy.sol](https://etherscan.io/address/0x9a016ce184a22dbf6c17daa59eb7d3140dbd1c54#code) + - [ccmproxy.sol](https://etherscan.io/address/0x5a51E2ebF8D136926b9cA7b59B60464E7C44d2Eb#code) + - [ccmCrossChainManager.sol](https://etherscan.io/address/0x14413419452Aaf089762A0c5e95eD2A13bBC488C#code) + +And modified for a modern solc so that they will run in our environment. + + ## Creating custom cross-chain messaging protocols Essentially there are 2 main interfaces on contracts that need to be satisfied to send a cross-chain message from a source chain to the target chain: diff --git a/smart-contracts/contracts/zilbridge/1/ccmCrossChainManager.sol b/smart-contracts/contracts/zilbridge/1/ccmCrossChainManager.sol new file mode 100644 index 0000000..c396c90 --- /dev/null +++ b/smart-contracts/contracts/zilbridge/1/ccmCrossChainManager.sol @@ -0,0 +1,1617 @@ +/** + *Submitted for verification at Etherscan.io on 2021-10-19 +*/ + +pragma solidity ^0.8.2; + +abstract contract Context { + // Empty internal constructor, to prevent people from mistakenly deploying + // an instance of this contract, which should be used via inheritance. + constructor () { } + // solhint-disable-previous-line no-empty-blocks + + function _msgSender() internal view returns (address payable) { + return payable(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; + } +} + +interface IOwnable { + function isOwner() external view returns (bool); +} + +abstract contract Ownable is Context, IOwnable { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor () { + 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; + } +} + +interface IPausable { + function paused() external view returns (bool); +} + +abstract contract Pausable is Context, IPausable { + /** + * @dev Emitted when the pause is triggered by a pauser (`account`). + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by a pauser (`account`). + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + constructor () { + _paused = false; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view returns (bool) { + return _paused; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!_paused, "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + */ + modifier whenPaused() { + require(_paused, "Pausable: not paused"); + _; + } + + /** + * @dev Called to pause, triggers stopped state. + */ + function _pause() internal whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Called to unpause, returns to normal state. + */ + function _unpause() internal whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } +} + +library ZeroCopySink { + /* @notice Convert boolean value into bytes + * @param b The boolean value + * @return Converted bytes array + */ + function WriteBool(bool b) internal pure returns (bytes memory) { + bytes memory buff; + assembly{ + buff := mload(0x40) + mstore(buff, 1) + switch iszero(b) + case 1 { + mstore(add(buff, 0x20), shl(248, 0x00)) + // mstore8(add(buff, 0x20), 0x00) + } + default { + mstore(add(buff, 0x20), shl(248, 0x01)) + // mstore8(add(buff, 0x20), 0x01) + } + mstore(0x40, add(buff, 0x21)) + } + return buff; + } + + /* @notice Convert byte value into bytes + * @param b The byte value + * @return Converted bytes array + */ + function WriteByte(bytes1 b) internal pure returns (bytes memory) { + return WriteUint8(uint8(b)); + } + + /* @notice Convert uint8 value into bytes + * @param v The uint8 value + * @return Converted bytes array + */ + function WriteUint8(uint8 v) internal pure returns (bytes memory) { + bytes memory buff; + assembly{ + buff := mload(0x40) + mstore(buff, 1) + mstore(add(buff, 0x20), shl(248, v)) + // mstore(add(buff, 0x20), byte(0x1f, v)) + mstore(0x40, add(buff, 0x21)) + } + return buff; + } + + /* @notice Convert uint16 value into bytes + * @param v The uint16 value + * @return Converted bytes array + */ + function WriteUint16(uint16 v) internal pure returns (bytes memory) { + bytes memory buff; + + assembly{ + buff := mload(0x40) + let byteLen := 0x02 + mstore(buff, byteLen) + for { + let mindex := 0x00 + let vindex := 0x1f + } lt(mindex, byteLen) { + mindex := add(mindex, 0x01) + vindex := sub(vindex, 0x01) + }{ + mstore8(add(add(buff, 0x20), mindex), byte(vindex, v)) + } + mstore(0x40, add(buff, 0x22)) + } + return buff; + } + + /* @notice Convert uint32 value into bytes + * @param v The uint32 value + * @return Converted bytes array + */ + function WriteUint32(uint32 v) internal pure returns(bytes memory) { + bytes memory buff; + assembly{ + buff := mload(0x40) + let byteLen := 0x04 + mstore(buff, byteLen) + for { + let mindex := 0x00 + let vindex := 0x1f + } lt(mindex, byteLen) { + mindex := add(mindex, 0x01) + vindex := sub(vindex, 0x01) + }{ + mstore8(add(add(buff, 0x20), mindex), byte(vindex, v)) + } + mstore(0x40, add(buff, 0x24)) + } + return buff; + } + + /* @notice Convert uint64 value into bytes + * @param v The uint64 value + * @return Converted bytes array + */ + function WriteUint64(uint64 v) internal pure returns(bytes memory) { + bytes memory buff; + + assembly{ + buff := mload(0x40) + let byteLen := 0x08 + mstore(buff, byteLen) + for { + let mindex := 0x00 + let vindex := 0x1f + } lt(mindex, byteLen) { + mindex := add(mindex, 0x01) + vindex := sub(vindex, 0x01) + }{ + mstore8(add(add(buff, 0x20), mindex), byte(vindex, v)) + } + mstore(0x40, add(buff, 0x28)) + } + return buff; + } + + /* @notice Convert limited uint256 value into bytes + * @param v The uint256 value + * @return Converted bytes array + */ + function WriteUint255(uint256 v) internal pure returns (bytes memory) { + require(v <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds uint255 range"); + bytes memory buff; + + assembly{ + buff := mload(0x40) + let byteLen := 0x20 + mstore(buff, byteLen) + for { + let mindex := 0x00 + let vindex := 0x1f + } lt(mindex, byteLen) { + mindex := add(mindex, 0x01) + vindex := sub(vindex, 0x01) + }{ + mstore8(add(add(buff, 0x20), mindex), byte(vindex, v)) + } + mstore(0x40, add(buff, 0x40)) + } + return buff; + } + + /* @notice Encode bytes format data into bytes + * @param data The bytes array data + * @return Encoded bytes array + */ + function WriteVarBytes(bytes memory data) internal pure returns (bytes memory) { + uint64 l = uint64(data.length); + return abi.encodePacked(WriteVarUint(l), data); + } + + function WriteVarUint(uint64 v) internal pure returns (bytes memory) { + if (v < 0xFD){ + return WriteUint8(uint8(v)); + } else if (v <= 0xFFFF) { + return abi.encodePacked(WriteByte(0xFD), WriteUint16(uint16(v))); + } else if (v <= 0xFFFFFFFF) { + return abi.encodePacked(WriteByte(0xFE), WriteUint32(uint32(v))); + } else { + return abi.encodePacked(WriteByte(0xFF), WriteUint64(uint64(v))); + } + } +} + +library ZeroCopySource { + /* @notice Read next byte as boolean type starting at offset from buff + * @param buff Source bytes array + * @param offset The position from where we read the boolean value + * @return The the read boolean value and new offset + */ + function NextBool(bytes memory buff, uint256 offset) internal pure returns(bool, uint256) { + require(offset + 1 <= buff.length && offset < offset + 1, "Offset exceeds limit"); + // byte === bytes1 + bytes1 v; + assembly{ + v := mload(add(add(buff, 0x20), offset)) + } + bool value; + if (v == 0x01) { + value = true; + } else if (v == 0x00) { + value = false; + } else { + revert("NextBool value error"); + } + return (value, offset + 1); + } + + /* @notice Read next byte starting at offset from buff + * @param buff Source bytes array + * @param offset The position from where we read the byte value + * @return The read byte value and new offset + */ + function NextByte(bytes memory buff, uint256 offset) internal pure returns (bytes1, uint256) { + require(offset + 1 <= buff.length && offset < offset + 1, "NextByte, Offset exceeds maximum"); + bytes1 v; + assembly{ + v := mload(add(add(buff, 0x20), offset)) + } + return (v, offset + 1); + } + + /* @notice Read next byte as uint8 starting at offset from buff + * @param buff Source bytes array + * @param offset The position from where we read the byte value + * @return The read uint8 value and new offset + */ + function NextUint8(bytes memory buff, uint256 offset) internal pure returns (uint8, uint256) { + require(offset + 1 <= buff.length && offset < offset + 1, "NextUint8, Offset exceeds maximum"); + uint8 v; + assembly{ + let tmpbytes := mload(0x40) + let bvalue := mload(add(add(buff, 0x20), offset)) + mstore8(tmpbytes, byte(0, bvalue)) + mstore(0x40, add(tmpbytes, 0x01)) + v := mload(sub(tmpbytes, 0x1f)) + } + return (v, offset + 1); + } + + /* @notice Read next two bytes as uint16 type starting from offset + * @param buff Source bytes array + * @param offset The position from where we read the uint16 value + * @return The read uint16 value and updated offset + */ + function NextUint16(bytes memory buff, uint256 offset) internal pure returns (uint16, uint256) { + require(offset + 2 <= buff.length && offset < offset + 2, "NextUint16, offset exceeds maximum"); + + uint16 v; + assembly { + let tmpbytes := mload(0x40) + let bvalue := mload(add(add(buff, 0x20), offset)) + mstore8(tmpbytes, byte(0x01, bvalue)) + mstore8(add(tmpbytes, 0x01), byte(0, bvalue)) + mstore(0x40, add(tmpbytes, 0x02)) + v := mload(sub(tmpbytes, 0x1e)) + } + return (v, offset + 2); + } + + + /* @notice Read next four bytes as uint32 type starting from offset + * @param buff Source bytes array + * @param offset The position from where we read the uint32 value + * @return The read uint32 value and updated offset + */ + function NextUint32(bytes memory buff, uint256 offset) internal pure returns (uint32, uint256) { + require(offset + 4 <= buff.length && offset < offset + 4, "NextUint32, offset exceeds maximum"); + uint32 v; + assembly { + let tmpbytes := mload(0x40) + let byteLen := 0x04 + for { + let tindex := 0x00 + let bindex := sub(byteLen, 0x01) + let bvalue := mload(add(add(buff, 0x20), offset)) + } lt(tindex, byteLen) { + tindex := add(tindex, 0x01) + bindex := sub(bindex, 0x01) + }{ + mstore8(add(tmpbytes, tindex), byte(bindex, bvalue)) + } + mstore(0x40, add(tmpbytes, byteLen)) + v := mload(sub(tmpbytes, sub(0x20, byteLen))) + } + return (v, offset + 4); + } + + /* @notice Read next eight bytes as uint64 type starting from offset + * @param buff Source bytes array + * @param offset The position from where we read the uint64 value + * @return The read uint64 value and updated offset + */ + function NextUint64(bytes memory buff, uint256 offset) internal pure returns (uint64, uint256) { + require(offset + 8 <= buff.length && offset < offset + 8, "NextUint64, offset exceeds maximum"); + uint64 v; + assembly { + let tmpbytes := mload(0x40) + let byteLen := 0x08 + for { + let tindex := 0x00 + let bindex := sub(byteLen, 0x01) + let bvalue := mload(add(add(buff, 0x20), offset)) + } lt(tindex, byteLen) { + tindex := add(tindex, 0x01) + bindex := sub(bindex, 0x01) + }{ + mstore8(add(tmpbytes, tindex), byte(bindex, bvalue)) + } + mstore(0x40, add(tmpbytes, byteLen)) + v := mload(sub(tmpbytes, sub(0x20, byteLen))) + } + return (v, offset + 8); + } + + /* @notice Read next 32 bytes as uint256 type starting from offset, + there are limits considering the numerical limits in multi-chain + * @param buff Source bytes array + * @param offset The position from where we read the uint256 value + * @return The read uint256 value and updated offset + */ + function NextUint255(bytes memory buff, uint256 offset) internal pure returns (uint256, uint256) { + require(offset + 32 <= buff.length && offset < offset + 32, "NextUint255, offset exceeds maximum"); + uint256 v; + assembly { + let tmpbytes := mload(0x40) + let byteLen := 0x20 + for { + let tindex := 0x00 + let bindex := sub(byteLen, 0x01) + let bvalue := mload(add(add(buff, 0x20), offset)) + } lt(tindex, byteLen) { + tindex := add(tindex, 0x01) + bindex := sub(bindex, 0x01) + }{ + mstore8(add(tmpbytes, tindex), byte(bindex, bvalue)) + } + mstore(0x40, add(tmpbytes, byteLen)) + v := mload(tmpbytes) + } + require(v <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds the range"); + return (v, offset + 32); + } + /* @notice Read next variable bytes starting from offset, + the decoding rule coming from multi-chain + * @param buff Source bytes array + * @param offset The position from where we read the bytes value + * @return The read variable bytes array value and updated offset + */ + function NextVarBytes(bytes memory buff, uint256 offset) internal pure returns(bytes memory, uint256) { + uint len; + (len, offset) = NextVarUint(buff, offset); + require(offset + len <= buff.length && offset < offset + len, "NextVarBytes, offset exceeds maximum"); + bytes memory tempBytes; + assembly{ + switch iszero(len) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(len, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, len) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(buff, lengthmod), mul(0x20, iszero(lengthmod))), offset) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, len) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return (tempBytes, offset + len); + } + /* @notice Read next 32 bytes starting from offset, + * @param buff Source bytes array + * @param offset The position from where we read the bytes value + * @return The read bytes32 value and updated offset + */ + function NextHash(bytes memory buff, uint256 offset) internal pure returns (bytes32 , uint256) { + require(offset + 32 <= buff.length && offset < offset + 32, "NextHash, offset exceeds maximum"); + bytes32 v; + assembly { + v := mload(add(buff, add(offset, 0x20))) + } + return (v, offset + 32); + } + + /* @notice Read next 20 bytes starting from offset, + * @param buff Source bytes array + * @param offset The position from where we read the bytes value + * @return The read bytes20 value and updated offset + */ + function NextBytes20(bytes memory buff, uint256 offset) internal pure returns (bytes20 , uint256) { + require(offset + 20 <= buff.length && offset < offset + 20, "NextBytes20, offset exceeds maximum"); + bytes20 v; + assembly { + v := mload(add(buff, add(offset, 0x20))) + } + return (v, offset + 20); + } + + function NextVarUint(bytes memory buff, uint256 offset) internal pure returns(uint, uint256) { + bytes1 v; + (v, offset) = NextByte(buff, offset); + + uint value; + if (v == 0xFD) { + // return NextUint16(buff, offset); + (value, offset) = NextUint16(buff, offset); + require(value >= 0xFD && value <= 0xFFFF, "NextUint16, value outside range"); + return (value, offset); + } else if (v == 0xFE) { + // return NextUint32(buff, offset); + (value, offset) = NextUint32(buff, offset); + require(value > 0xFFFF && value <= 0xFFFFFFFF, "NextVarUint, value outside range"); + return (value, offset); + } else if (v == 0xFF) { + // return NextUint64(buff, offset); + (value, offset) = NextUint64(buff, offset); + require(value > 0xFFFFFFFF, "NextVarUint, value outside range"); + return (value, offset); + } else{ + // return (uint8(v), offset); + value = uint8(v); + require(value < 0xFD, "NextVarUint, value outside range"); + return (value, offset); + } + } +} + +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; + } +} + + +library Utils { + + /* @notice Convert the bytes array to bytes32 type, the bytes array length must be 32 + * @param _bs Source bytes array + * @return bytes32 + */ + function bytesToBytes32(bytes memory _bs) internal pure returns (bytes32 value) { + require(_bs.length == 32, "bytes length is not 32."); + assembly { + // load 32 bytes from memory starting from position _bs + 0x20 since the first 0x20 bytes stores _bs length + value := mload(add(_bs, 0x20)) + } + } + + /* @notice Convert bytes to uint256 + * @param _b Source bytes should have length of 32 + * @return uint256 + */ + function bytesToUint256(bytes memory _bs) internal pure returns (uint256 value) { + require(_bs.length == 32, "bytes length is not 32."); + assembly { + // load 32 bytes from memory starting from position _bs + 32 + value := mload(add(_bs, 0x20)) + } + require(value <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds the range"); + } + + /* @notice Convert uint256 to bytes + * @param _b uint256 that needs to be converted + * @return bytes + */ + function uint256ToBytes(uint256 _value) internal pure returns (bytes memory bs) { + require(_value <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds the range"); + assembly { + // Get a location of some free memory and store it in result as + // Solidity does for memory variables. + bs := mload(0x40) + // Put 0x20 at the first word, the length of bytes for uint256 value + mstore(bs, 0x20) + //In the next word, put value in bytes format to the next 32 bytes + mstore(add(bs, 0x20), _value) + // Update the free-memory pointer by padding our last write location to 32 bytes + mstore(0x40, add(bs, 0x40)) + } + } + + /* @notice Convert bytes to address + * @param _bs Source bytes: bytes length must be 20 + * @return Converted address from source bytes + */ + function bytesToAddress(bytes memory _bs) internal pure returns (address addr) + { + require(_bs.length == 20, "bytes length does not match address"); + assembly { + // for _bs, first word store _bs.length, second word store _bs.value + // load 32 bytes from mem[_bs+20], convert it into Uint160, meaning we take last 20 bytes as addr (address). + addr := mload(add(_bs, 0x14)) + } + + } + + /* @notice Convert address to bytes + * @param _addr Address need to be converted + * @return Converted bytes from address + */ + function addressToBytes(address _addr) internal pure returns (bytes memory bs){ + assembly { + // Get a location of some free memory and store it in result as + // Solidity does for memory variables. + bs := mload(0x40) + // Put 20 (address byte length) at the first word, the length of bytes for uint256 value + mstore(bs, 0x14) + // logical shift left _a by 12 bytes, change _a from right-aligned to left-aligned + mstore(add(bs, 0x20), shl(96, _addr)) + // Update the free-memory pointer by padding our last write location to 32 bytes + mstore(0x40, add(bs, 0x40)) + } + } + + /* @notice Do hash leaf as the multi-chain does + * @param _data Data in bytes format + * @return Hashed value in bytes32 format + */ + function hashLeaf(bytes memory _data) internal pure returns (bytes32 result) { + result = sha256(abi.encodePacked(bytes1(0x0), _data)); + } + + /* @notice Do hash children as the multi-chain does + * @param _l Left node + * @param _r Right node + * @return Hashed value in bytes32 format + */ + function hashChildren(bytes32 _l, bytes32 _r) internal pure returns (bytes32 result) { + result = sha256(abi.encodePacked(bytes1(0x01), _l, _r)); + } + + /* @notice Compare if two bytes are equal, which are in storage and memory, seperately + Refer from https://github.com/summa-tx/bitcoin-spv/blob/master/solidity/contracts/BytesLib.sol#L368 + * @param _preBytes The bytes stored in storage + * @param _postBytes The bytes stored in memory + * @return Bool type indicating if they are equal + */ + function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) { + bool success = true; + + assembly { + // we know _preBytes_offset is 0 + let fslot := sload(_preBytes.slot) + // Arrays of 31 bytes or less have an even value in their slot, + // while longer arrays have an odd value. The actual length is + // the slot divided by two for odd values, and the lowest order + // byte divided by two for even values. + // If the slot is even, bitwise and the slot with 255 and divide by + // two to get the length. If the slot is odd, bitwise and the slot + // with -1 and divide by two. + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) + let mlength := mload(_postBytes) + + // if lengths don't match the arrays are not equal + switch eq(slength, mlength) + case 1 { + // fslot can contain both the length and contents of the array + // if slength < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + // slength != 0 + if iszero(iszero(slength)) { + switch lt(slength, 32) + case 1 { + // blank the last byte which is the length + fslot := mul(div(fslot, 0x100), 0x100) + + if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { + // unsuccess: + success := 0 + } + } + default { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + let sc := keccak256(0x0, 0x20) + + let mc := add(_postBytes, 0x20) + let end := add(mc, mlength) + + // the next line is the loop condition: + // while(uint(mc < end) + cb == 2) + for {} eq(add(lt(mc, end), cb), 2) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + if iszero(eq(sload(sc), mload(mc))) { + // unsuccess: + success := 0 + cb := 0 + } + } + } + } + } + default { + // unsuccess: + success := 0 + } + } + + return success; + } + + /* @notice Slice the _bytes from _start index till the result has length of _length + Refer from https://github.com/summa-tx/bitcoin-spv/blob/master/solidity/contracts/BytesLib.sol#L246 + * @param _bytes The original bytes needs to be sliced + * @param _start The index of _bytes for the start of sliced bytes + * @param _length The index of _bytes for the end of sliced bytes + * @return The sliced bytes + */ + function slice( + bytes memory _bytes, + uint _start, + uint _length + ) + internal + pure + returns (bytes memory) + { + require(_bytes.length >= (_start + _length)); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + // lengthmod <= _length % 32 + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + /* @notice Check if the elements number of _signers within _keepers array is no less than _m + * @param _keepers The array consists of serveral address + * @param _signers Some specific addresses to be looked into + * @param _m The number requirement paramter + * @return True means containment, false meansdo do not contain. + */ + function containMAddresses(address[] memory _keepers, address[] memory _signers, uint _m) internal pure returns (bool){ + uint m = 0; + for(uint i = 0; i < _signers.length; i++){ + for (uint j = 0; j < _keepers.length; j++) { + if (_signers[i] == _keepers[j]) { + m++; + delete _keepers[j]; + } + } + } + return m >= _m; + } + + /* @notice TODO + * @param key + * @return + */ + function compressMCPubKey(bytes memory key) internal pure returns (bytes memory newkey) { + require(key.length >= 67, "key lenggh is too short"); + newkey = slice(key, 0, 35); + if (uint8(key[66]) % 2 == 0){ + newkey[2] = bytes1(0x02); + } else { + newkey[2] = bytes1(0x03); + } + return newkey; + } + + /** + * @dev Returns true if `account` is a contract. + * Refer from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L18 + * + * This test is non-exhaustive, and there may be false-negatives: during the + * execution of a contract's constructor, its address will be reported as + * not containing a contract. + * + * IMPORTANT: It is unsafe to assume that an address for which this + * function returns false is an externally-owned account (EOA) and not a + * contract. + */ + function isContract(address account) internal view returns (bool) { + // This method relies in extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != 0x0 && codehash != accountHash); + } +} + +library ECCUtils { + using SafeMath for uint256; + + struct Header { + uint32 version; + uint64 chainId; + uint32 timestamp; + uint32 height; + uint64 consensusData; + bytes32 prevBlockHash; + bytes32 transactionsRoot; + bytes32 crossStatesRoot; + bytes32 blockRoot; + bytes consensusPayload; + bytes20 nextBookkeeper; + } + + struct ToMerkleValue { + bytes txHash; // cross chain txhash + uint64 fromChainID; + TxParam makeTxParam; + } + + struct TxParam { + bytes txHash; // source chain txhash + bytes crossChainId; + bytes fromContract; + uint64 toChainId; + bytes toContract; + bytes method; + bytes args; + } + + uint constant POLYCHAIN_PUBKEY_LEN = 67; + uint constant POLYCHAIN_SIGNATURE_LEN = 65; + + /* @notice Verify Poly chain transaction whether exist or not + * @param _auditPath Poly chain merkle proof + * @param _root Poly chain root + * @return The verified value included in _auditPath + */ + function merkleProve(bytes memory _auditPath, bytes32 _root) internal pure returns (bytes memory) { + uint256 off = 0; + bytes memory value; + (value, off) = ZeroCopySource.NextVarBytes(_auditPath, off); + + bytes32 hash = Utils.hashLeaf(value); + uint size = _auditPath.length.sub(off).div(33); + bytes32 nodeHash; + bytes1 pos; + for (uint i = 0; i < size; i++) { + (pos, off) = ZeroCopySource.NextByte(_auditPath, off); + (nodeHash, off) = ZeroCopySource.NextHash(_auditPath, off); + if (pos == 0x00) { + hash = Utils.hashChildren(nodeHash, hash); + } else if (pos == 0x01) { + hash = Utils.hashChildren(hash, nodeHash); + } else { + revert("merkleProve, NextByte for position info failed"); + } + } + require(hash == _root, "merkleProve, expect root is not equal actual root"); + return value; + } + + /* @notice calculate next book keeper according to public key list + * @param _keyLen consensus node number + * @param _m minimum signature number + * @param _pubKeyList consensus node public key list + * @return two element: next book keeper, consensus node signer addresses + */ + function _getBookKeeper(uint _keyLen, uint _m, bytes memory _pubKeyList) internal pure returns (bytes20, address[] memory){ + bytes memory buff; + buff = ZeroCopySink.WriteUint16(uint16(_keyLen)); + address[] memory keepers = new address[](_keyLen); + bytes32 hash; + bytes memory publicKey; + for(uint i = 0; i < _keyLen; i++){ + publicKey = Utils.slice(_pubKeyList, i*POLYCHAIN_PUBKEY_LEN, POLYCHAIN_PUBKEY_LEN); + buff = abi.encodePacked(buff, ZeroCopySink.WriteVarBytes(Utils.compressMCPubKey(publicKey))); + hash = keccak256(Utils.slice(publicKey, 3, 64)); + keepers[i] = address(uint160(uint256(hash))); + } + + buff = abi.encodePacked(buff, ZeroCopySink.WriteUint16(uint16(_m))); + bytes20 nextBookKeeper = ripemd160(abi.encodePacked(sha256(buff))); + return (nextBookKeeper, keepers); + } + + /* @notice Verify public key derived from Poly chain + * @param _pubKeyList serialized consensus node public key list + * @param _sigList consensus node signature list + * @return return two element: next book keeper, consensus node signer addresses + */ + function verifyPubkey(bytes memory _pubKeyList) internal pure returns (bytes20, address[] memory) { + require(_pubKeyList.length % POLYCHAIN_PUBKEY_LEN == 0, "_pubKeyList length illegal!"); + uint n = _pubKeyList.length / POLYCHAIN_PUBKEY_LEN; + require(n >= 1, "too short _pubKeyList!"); + return _getBookKeeper(n, n - (n - 1) / 3, _pubKeyList); + } + + /* @notice Verify Poly chain consensus node signature + * @param _rawHeader Poly chain block header raw bytes + * @param _sigList consensus node signature list + * @param _keepers addresses corresponding with Poly chain book keepers' public keys + * @param _m minimum signature number + * @return true or false + */ + function verifySig(bytes memory _rawHeader, bytes memory _sigList, address[] memory _keepers, uint _m) internal pure returns (bool){ + bytes32 hash = getHeaderHash(_rawHeader); + + uint sigCount = _sigList.length.div(POLYCHAIN_SIGNATURE_LEN); + address[] memory signers = new address[](sigCount); + bytes32 r; + bytes32 s; + uint8 v; + for(uint j = 0; j < sigCount; j++){ + r = Utils.bytesToBytes32(Utils.slice(_sigList, j*POLYCHAIN_SIGNATURE_LEN, 32)); + s = Utils.bytesToBytes32(Utils.slice(_sigList, j*POLYCHAIN_SIGNATURE_LEN + 32, 32)); + v = uint8(_sigList[j*POLYCHAIN_SIGNATURE_LEN + 64]) + 27; + signers[j] = ecrecover(sha256(abi.encodePacked(hash)), v, r, s); + if (signers[j] == address(0)) return false; + } + return Utils.containMAddresses(_keepers, signers, _m); + } + + + /* @notice Serialize Poly chain book keepers' info in Ethereum addresses format into raw bytes + * @param keepersBytes The serialized addresses + * @return serialized bytes result + */ + function serializeKeepers(address[] memory keepers) internal pure returns (bytes memory) { + uint256 keeperLen = keepers.length; + bytes memory keepersBytes = ZeroCopySink.WriteUint64(uint64(keeperLen)); + for(uint i = 0; i < keeperLen; i++) { + keepersBytes = abi.encodePacked(keepersBytes, ZeroCopySink.WriteVarBytes(Utils.addressToBytes(keepers[i]))); + } + return keepersBytes; + } + + /* @notice Deserialize bytes into Ethereum addresses + * @param keepersBytes The serialized addresses derived from Poly chain book keepers in bytes format + * @return addresses + */ + function deserializeKeepers(bytes memory keepersBytes) internal pure returns (address[] memory) { + uint256 off = 0; + uint64 keeperLen; + (keeperLen, off) = ZeroCopySource.NextUint64(keepersBytes, off); + address[] memory keepers = new address[](keeperLen); + bytes memory keeperBytes; + for(uint i = 0; i < keeperLen; i++) { + (keeperBytes, off) = ZeroCopySource.NextVarBytes(keepersBytes, off); + keepers[i] = Utils.bytesToAddress(keeperBytes); + } + return keepers; + } + + /* @notice Deserialize Poly chain transaction raw value + * @param _valueBs Poly chain transaction raw bytes + * @return ToMerkleValue struct + */ + function deserializeMerkleValue(bytes memory _valueBs) internal pure returns (ToMerkleValue memory) { + ToMerkleValue memory toMerkleValue; + uint256 off = 0; + + (toMerkleValue.txHash, off) = ZeroCopySource.NextVarBytes(_valueBs, off); + + (toMerkleValue.fromChainID, off) = ZeroCopySource.NextUint64(_valueBs, off); + + TxParam memory txParam; + + (txParam.txHash, off) = ZeroCopySource.NextVarBytes(_valueBs, off); + + (txParam.crossChainId, off) = ZeroCopySource.NextVarBytes(_valueBs, off); + + (txParam.fromContract, off) = ZeroCopySource.NextVarBytes(_valueBs, off); + + (txParam.toChainId, off) = ZeroCopySource.NextUint64(_valueBs, off); + + (txParam.toContract, off) = ZeroCopySource.NextVarBytes(_valueBs, off); + + (txParam.method, off) = ZeroCopySource.NextVarBytes(_valueBs, off); + + (txParam.args, off) = ZeroCopySource.NextVarBytes(_valueBs, off); + toMerkleValue.makeTxParam = txParam; + + return toMerkleValue; + } + + /* @notice Deserialize Poly chain block header raw bytes + * @param _valueBs Poly chain block header raw bytes + * @return Header struct + */ + function deserializeHeader(bytes memory _headerBs) internal pure returns (Header memory) { + Header memory header; + uint256 off = 0; + (header.version, off) = ZeroCopySource.NextUint32(_headerBs, off); + + (header.chainId, off) = ZeroCopySource.NextUint64(_headerBs, off); + + (header.prevBlockHash, off) = ZeroCopySource.NextHash(_headerBs, off); + + (header.transactionsRoot, off) = ZeroCopySource.NextHash(_headerBs, off); + + (header.crossStatesRoot, off) = ZeroCopySource.NextHash(_headerBs, off); + + (header.blockRoot, off) = ZeroCopySource.NextHash(_headerBs, off); + + (header.timestamp, off) = ZeroCopySource.NextUint32(_headerBs, off); + + (header.height, off) = ZeroCopySource.NextUint32(_headerBs, off); + + (header.consensusData, off) = ZeroCopySource.NextUint64(_headerBs, off); + + (header.consensusPayload, off) = ZeroCopySource.NextVarBytes(_headerBs, off); + + (header.nextBookkeeper, off) = ZeroCopySource.NextBytes20(_headerBs, off); + + return header; + } + + /* @notice Deserialize Poly chain block header raw bytes + * @param rawHeader Poly chain block header raw bytes + * @return header hash same as Poly chain + */ + function getHeaderHash(bytes memory rawHeader) internal pure returns (bytes32) { + return sha256(abi.encodePacked(sha256(rawHeader))); + } +} + +interface IEthCrossChainData { + function putCurEpochStartHeight(uint32 curEpochStartHeight) external returns (bool); + function getCurEpochStartHeight() external view returns (uint32); + function putCurEpochConPubKeyBytes(bytes calldata curEpochPkBytes) external returns (bool); + function getCurEpochConPubKeyBytes() external view returns (bytes memory); + function markFromChainTxExist(uint64 fromChainId, bytes32 fromChainTx) external returns (bool); + function checkIfFromChainTxExist(uint64 fromChainId, bytes32 fromChainTx) external view returns (bool); + function getEthTxHashIndex() external view returns (uint256); + function putEthTxHash(bytes32 ethTxHash) external returns (bool); + function putExtraData(bytes32 key1, bytes32 key2, bytes calldata value) external returns (bool); + function getExtraData(bytes32 key1, bytes32 key2) external view returns (bytes memory); + function transferOwnership(address newOwner) external; + function pause() external returns (bool); + function unpause() external returns (bool); + function paused() external view returns (bool); + // Not used currently by ECCM + function getEthTxHash(uint256 ethTxHashIndex) external view returns (bytes32); +} + +interface IUpgradableECCM is IOwnable, IPausable { + function pause() external returns (bool); + function unpause() external returns (bool); + function upgradeToNew(address) external returns (bool); + function setChainId(uint64 _newChainId) external returns (bool); +} + + +interface IEthCrossChainManager { + function crossChain(uint64 _toChainId, bytes calldata _toContract, bytes calldata _method, bytes calldata _txData) external returns (bool); +} + +contract UpgradableECCM is IUpgradableECCM, Ownable, Pausable { + address public EthCrossChainDataAddress; + uint64 public chainId; + + constructor (address ethCrossChainDataAddr, uint64 _chainId) Pausable() Ownable() { + EthCrossChainDataAddress = ethCrossChainDataAddr; + chainId = _chainId; + } + function pause() onlyOwner public returns (bool) { + if (!paused()) { + _pause(); + } + IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); + if (!eccd.paused()) { + require(eccd.pause(), "pause EthCrossChainData contract failed"); + } + return true; + } + + function unpause() onlyOwner public returns (bool) { + if (paused()) { + _unpause(); + } + IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); + if (eccd.paused()) { + require(eccd.unpause(), "unpause EthCrossChainData contract failed"); + } + return true; + } + + // if we want to upgrade this contract, we need to invoke this method + function upgradeToNew(address newEthCrossChainManagerAddress) whenPaused onlyOwner public returns (bool) { + IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); + eccd.transferOwnership(newEthCrossChainManagerAddress); + return true; + } + + function setChainId(uint64 _newChainId) whenPaused onlyOwner public returns (bool) { + chainId = _newChainId; + return true; + } +} + +contract EthCrossChainManager is IEthCrossChainManager, UpgradableECCM { + using SafeMath for uint256; + + address public whiteLister; + mapping(address => bool) public whiteListFromContract; + mapping(address => mapping(bytes => bool)) public whiteListContractMethodMap; + + event InitGenesisBlockEvent(uint256 height, bytes rawHeader); + event ChangeBookKeeperEvent(uint256 height, bytes rawHeader); + event CrossChainEvent(address indexed sender, bytes txId, address proxyOrAssetContract, uint64 toChainId, bytes toContract, bytes rawdata); + event VerifyHeaderAndExecuteTxEvent(uint64 fromChainID, bytes toContract, bytes crossChainTxHash, bytes fromChainTxHash); + constructor( + address _eccd, + uint64 _chainId, + address[] memory fromContractWhiteList, + bytes[] memory contractMethodWhiteList + ) UpgradableECCM(_eccd,_chainId) { + whiteLister = msg.sender; + for (uint i=0;i curEpochStartHeight, "The height of header is lower than current epoch start height!"); + + // Ensure the rawHeader is the key header including info of switching consensus peers by containing non-empty nextBookKeeper field + require(header.nextBookkeeper != bytes20(0), "The nextBookKeeper of header is empty"); + + // Verify signature of rawHeader comes from pubKeyList + address[] memory polyChainBKs = ECCUtils.deserializeKeepers(eccd.getCurEpochConPubKeyBytes()); + uint n = polyChainBKs.length; + require(ECCUtils.verifySig(rawHeader, sigList, polyChainBKs, n - (n - 1) / 3), "Verify signature failed!"); + + // Convert pubKeyList into ethereum address format and make sure the compound address from the converted ethereum addresses + // equals passed in header.nextBooker + (bytes20 nextBookKeeper, address[] memory keepers) = ECCUtils.verifyPubkey(pubKeyList); + require(header.nextBookkeeper == nextBookKeeper, "NextBookers illegal"); + + // update current epoch start height of Poly chain and current epoch consensus peers book keepers addresses + require(eccd.putCurEpochStartHeight(header.height), "Save MC LatestHeight to Data contract failed!"); + require(eccd.putCurEpochConPubKeyBytes(ECCUtils.serializeKeepers(keepers)), "Save Poly chain book keepers bytes to Data contract failed!"); + + // Fire the change book keeper event + emit ChangeBookKeeperEvent(header.height, rawHeader); + return true; + } + + + /* @notice ERC20 token cross chain to other blockchain. + * this function push tx event to blockchain + * @param toChainId Target chain id + * @param toContract Target smart contract address in target block chain + * @param txData Transaction data for target chain, include to_address, amount + * @return true or false + */ + function crossChain(uint64 toChainId, bytes calldata toContract, bytes calldata method, bytes calldata txData) whenNotPaused external returns (bool) { + // Only allow whitelist contract to call + require(whiteListFromContract[msg.sender],"Invalid from contract"); + + // Load Ethereum cross chain data contract + IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); + + // To help differentiate two txs, the ethTxHashIndex is increasing automatically + uint256 txHashIndex = eccd.getEthTxHashIndex(); + + // Convert the uint256 into bytes + bytes memory paramTxHash = Utils.uint256ToBytes(txHashIndex); + + // Construct the makeTxParam, and put the hash info storage, to help provide proof of tx existence + bytes memory rawParam = abi.encodePacked(ZeroCopySink.WriteVarBytes(paramTxHash), + ZeroCopySink.WriteVarBytes(abi.encodePacked(sha256(abi.encodePacked(address(this), paramTxHash)))), + ZeroCopySink.WriteVarBytes(Utils.addressToBytes(msg.sender)), + ZeroCopySink.WriteUint64(toChainId), + ZeroCopySink.WriteVarBytes(toContract), + ZeroCopySink.WriteVarBytes(method), + ZeroCopySink.WriteVarBytes(txData) + ); + + // Must save it in the storage to be included in the proof to be verified. + require(eccd.putEthTxHash(keccak256(rawParam)), "Save ethTxHash by index to Data contract failed!"); + + // Fire the cross chain event denoting there is a cross chain request from Ethereum network to other public chains through Poly chain network + emit CrossChainEvent(tx.origin, paramTxHash, msg.sender, toChainId, toContract, rawParam); + return true; + } + /* @notice Verify Poly chain header and proof, execute the cross chain tx from Poly chain to Ethereum + * @param proof Poly chain tx merkle proof + * @param rawHeader The header containing crossStateRoot to verify the above tx merkle proof + * @param headerProof The header merkle proof used to verify rawHeader + * @param curRawHeader Any header in current epoch consensus of Poly chain + * @param headerSig The coverted signature veriable for solidity derived from Poly chain consensus nodes' signature + * used to verify the validity of curRawHeader + * @return true or false + */ + function verifyHeaderAndExecuteTx(bytes memory proof, bytes memory rawHeader, bytes memory headerProof, bytes memory curRawHeader,bytes memory headerSig) whenNotPaused public returns (bool){ + ECCUtils.Header memory header = ECCUtils.deserializeHeader(rawHeader); + // Load ehereum cross chain data contract + IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); + + // Get stored consensus public key bytes of current poly chain epoch and deserialize Poly chain consensus public key bytes to address[] + address[] memory polyChainBKs = ECCUtils.deserializeKeepers(eccd.getCurEpochConPubKeyBytes()); + + uint256 curEpochStartHeight = eccd.getCurEpochStartHeight(); + + uint n = polyChainBKs.length; + if (header.height >= curEpochStartHeight) { + // It's enough to verify rawHeader signature + require(ECCUtils.verifySig(rawHeader, headerSig, polyChainBKs, n - ( n - 1) / 3), "Verify poly chain header signature failed!"); + } else { + // We need to verify the signature of curHeader + require(ECCUtils.verifySig(curRawHeader, headerSig, polyChainBKs, n - ( n - 1) / 3), "Verify poly chain current epoch header signature failed!"); + + // Then use curHeader.StateRoot and headerProof to verify rawHeader.CrossStateRoot + ECCUtils.Header memory curHeader = ECCUtils.deserializeHeader(curRawHeader); + bytes memory proveValue = ECCUtils.merkleProve(headerProof, curHeader.blockRoot); + require(ECCUtils.getHeaderHash(rawHeader) == Utils.bytesToBytes32(proveValue), "verify header proof failed!"); + } + + // Through rawHeader.CrossStatesRoot, the toMerkleValue or cross chain msg can be verified and parsed from proof + bytes memory toMerkleValueBs = ECCUtils.merkleProve(proof, header.crossStatesRoot); + + // Parse the toMerkleValue struct and make sure the tx has not been processed, then mark this tx as processed + ECCUtils.ToMerkleValue memory toMerkleValue = ECCUtils.deserializeMerkleValue(toMerkleValueBs); + require(!eccd.checkIfFromChainTxExist(toMerkleValue.fromChainID, Utils.bytesToBytes32(toMerkleValue.txHash)), "the transaction has been executed!"); + require(eccd.markFromChainTxExist(toMerkleValue.fromChainID, Utils.bytesToBytes32(toMerkleValue.txHash)), "Save crosschain tx exist failed!"); + + // Ethereum ChainId is 2, we need to check the transaction is for Ethereum network + require(toMerkleValue.makeTxParam.toChainId == chainId, "This Tx is not aiming at this network!"); + + // Obtain the targeting contract, so that Ethereum cross chain manager contract can trigger the executation of cross chain tx on Ethereum side + address toContract = Utils.bytesToAddress(toMerkleValue.makeTxParam.toContract); + + // only invoke PreWhiteListed Contract and method For Now + require(whiteListContractMethodMap[toContract][toMerkleValue.makeTxParam.method],"Invalid to contract or method"); + + //TODO: check this part to make sure we commit the next line when doing local net UT test + require(_executeCrossChainTx(toContract, toMerkleValue.makeTxParam.method, toMerkleValue.makeTxParam.args, toMerkleValue.makeTxParam.fromContract, toMerkleValue.fromChainID), "Execute CrossChain Tx failed!"); + + // Fire the cross chain event denoting the executation of cross chain tx is successful, + // and this tx is coming from other public chains to current Ethereum network + emit VerifyHeaderAndExecuteTxEvent(toMerkleValue.fromChainID, toMerkleValue.makeTxParam.toContract, toMerkleValue.txHash, toMerkleValue.makeTxParam.txHash); + + return true; + } + + /* @notice Dynamically invoke the targeting contract, and trigger executation of cross chain tx on Ethereum side + * @param _toContract The targeting contract that will be invoked by the Ethereum Cross Chain Manager contract + * @param _method At which method will be invoked within the targeting contract + * @param _args The parameter that will be passed into the targeting contract + * @param _fromContractAddr From chain smart contract address + * @param _fromChainId Indicate from which chain current cross chain tx comes + * @return true or false + */ + function _executeCrossChainTx(address _toContract, bytes memory _method, bytes memory _args, bytes memory _fromContractAddr, uint64 _fromChainId) internal returns (bool){ + // Ensure the targeting contract gonna be invoked is indeed a contract rather than a normal account address + require(Utils.isContract(_toContract), "The passed in address is not a contract!"); + bytes memory returnData; + bool success; + + // The returnData will be bytes32, the last byte must be 01; + (success, returnData) = _toContract.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_args, _fromContractAddr, _fromChainId))); + + // Ensure the executation is successful + require(success == true, "EthCrossChain call business contract failed"); + + // Ensure the returned value is true + require(returnData.length != 0, "No return value from business contract!"); + (bool res,) = ZeroCopySource.NextBool(returnData, 31); + require(res == true, "EthCrossChain call business contract return is not true"); + + return true; + } +} diff --git a/smart-contracts/contracts/zilbridge/1/ccmProxy.sol b/smart-contracts/contracts/zilbridge/1/ccmProxy.sol new file mode 100644 index 0000000..08a27f6 --- /dev/null +++ b/smart-contracts/contracts/zilbridge/1/ccmProxy.sol @@ -0,0 +1,265 @@ +/** + *Submitted for verification at Etherscan.io on 2023-02-06 +*/ + +// File: eth-contracts/contracts/core/cross_chain_manager/interface/IEthCrossChainManagerProxy.sol + +//pragma solidity ^0.5.0; +pragma solidity ^0.8.20; + +/** + * @dev Interface of the EthCrossChainManagerProxy for business contract like LockProxy to obtain the reliable EthCrossChainManager contract hash. + */ +interface IEthCrossChainManagerProxy { + function getEthCrossChainManager() external view returns (address); +} + +// File: eth-contracts/contracts/core/cross_chain_manager/interface/IUpgradableECCM.sol + +/** + * @dev Interface of upgradableECCM to make ECCM be upgradable, the implementation is in UpgradableECCM.sol + */ +interface IUpgradableECCM { + function pause() external returns (bool); + function unpause() external returns (bool); + function paused() external view returns (bool); + function upgradeToNew(address) external returns (bool); + function isOwner() external view returns (bool); + function setChainId(uint64 _newChainId) external returns (bool); +} + +// File: eth-contracts/contracts/libs/GSN/Context.sol + + +/* + * @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. + * Refer from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/GSN/Context.sol + */ +abstract contract Context { + // Empty internal constructor, to prevent people from mistakenly deploying + // an instance of this contract, which should be used via inheritance. + constructor () { } + // solhint-disable-previous-line no-empty-blocks + + function _msgSender() internal view returns (address payable) { + return payable(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; + } +} + +// File: eth-contracts/contracts/libs/lifecycle/Pausable.sol + + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Context { + /** + * @dev Emitted when the pause is triggered by a pauser (`account`). + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by a pauser (`account`). + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + constructor () { + _paused = false; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view returns (bool) { + return _paused; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!_paused, "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + */ + modifier whenPaused() { + require(_paused, "Pausable: not paused"); + _; + } + + /** + * @dev Called to pause, triggers stopped state. + */ + function _pause() internal whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Called to unpause, returns to normal state. + */ + function _unpause() internal whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } +} + +// File: eth-contracts/contracts/libs/ownership/Ownable.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. + */ +abstract 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 () { + 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; + } +} + +// File: eth-contracts/contracts/core/cross_chain_manager/upgrade/EthCrossChainManagerProxy.sol + + + + + + +contract EthCrossChainManagerProxy is IEthCrossChainManagerProxy, Ownable, Pausable { + address private EthCrossChainManagerAddr_; + + constructor(address _ethCrossChainManagerAddr) { + EthCrossChainManagerAddr_ = _ethCrossChainManagerAddr; + } + + function pause() onlyOwner public returns (bool) { + if (paused()) { + return true; + } + _pause(); + return true; + } + function unpause() onlyOwner public returns (bool) { + if (!paused()) { + return true; + } + _unpause(); + return true; + } + function pauseEthCrossChainManager() onlyOwner whenNotPaused public returns (bool) { + IUpgradableECCM eccm = IUpgradableECCM(EthCrossChainManagerAddr_); + require(pause(), "pause EthCrossChainManagerProxy contract failed!"); + require(eccm.pause(), "pause EthCrossChainManager contract failed!"); + return true; + } + function upgradeEthCrossChainManager(address _newEthCrossChainManagerAddr) onlyOwner whenPaused public returns (bool) { + IUpgradableECCM eccm = IUpgradableECCM(EthCrossChainManagerAddr_); + if (!eccm.paused()) { + require(eccm.pause(), "Pause old EthCrossChainManager contract failed!"); + } + require(eccm.upgradeToNew(_newEthCrossChainManagerAddr), "EthCrossChainManager upgradeToNew failed!"); + IUpgradableECCM neweccm = IUpgradableECCM(_newEthCrossChainManagerAddr); + require(neweccm.isOwner(), "EthCrossChainManagerProxy is not owner of new EthCrossChainManager contract"); + EthCrossChainManagerAddr_ = _newEthCrossChainManagerAddr; + return true; + } + function unpauseEthCrossChainManager() onlyOwner whenPaused public returns (bool) { + IUpgradableECCM eccm = IUpgradableECCM(EthCrossChainManagerAddr_); + require(eccm.unpause(), "unpause EthCrossChainManager contract failed!"); + require(unpause(), "unpause EthCrossChainManagerProxy contract failed!"); + return true; + } + function getEthCrossChainManager() whenNotPaused public view returns (address) { + return EthCrossChainManagerAddr_; + } +} diff --git a/smart-contracts/contracts/zilbridge/1/ethCrossChainData.sol b/smart-contracts/contracts/zilbridge/1/ethCrossChainData.sol new file mode 100644 index 0000000..788d67b --- /dev/null +++ b/smart-contracts/contracts/zilbridge/1/ethCrossChainData.sol @@ -0,0 +1,315 @@ +/** + *Submitted for verification at Etherscan.io on 2023-02-06 +*/ + +// File: eth-contracts/contracts/core/cross_chain_manager/interface/IEthCrossChainData.sol + +pragma solidity ^0.8.2; + + +interface IPausable { + function paused() external view returns (bool); + function pause() external returns (bool); + function unpause() external returns (bool); +} + +interface IOwnable { + function isOwner() external view returns (bool); + function transferOwnership(address newOwner) external; +} + + +/** + * @dev Interface of the EthCrossChainData contract, the implementation is in EthCrossChainData.sol + */ +interface IEthCrossChainData is IPausable { + function putCurEpochStartHeight(uint32 curEpochStartHeight) external returns (bool); + function getCurEpochStartHeight() external view returns (uint32); + function putCurEpochConPubKeyBytes(bytes calldata curEpochPkBytes) external returns (bool); + function getCurEpochConPubKeyBytes() external view returns (bytes memory); + function markFromChainTxExist(uint64 fromChainId, bytes32 fromChainTx) external returns (bool); + function checkIfFromChainTxExist(uint64 fromChainId, bytes32 fromChainTx) external view returns (bool); + function getEthTxHashIndex() external view returns (uint256); + function putEthTxHash(bytes32 ethTxHash) external returns (bool); + function putExtraData(bytes32 key1, bytes32 key2, bytes calldata value) external returns (bool); + function getExtraData(bytes32 key1, bytes32 key2) external view returns (bytes memory); + // function transferOwnership(address newOwner) external; + // Not used currently by ECCM + function getEthTxHash(uint256 ethTxHashIndex) external view returns (bytes32); +} +// File: eth-contracts/contracts/libs/GSN/Context.sol + +/* + * @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. + * Refer from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/GSN/Context.sol + */ +abstract contract Context { + // solhint-disable-previous-line no-empty-blocks + + function _msgSender() internal view returns (address payable) { + return payable(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; + } +} + +// File: eth-contracts/contracts/libs/lifecycle/Pausable.sol + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Context, IPausable { + /** + * @dev Emitted when the pause is triggered by a pauser (`account`). + */ + event Paused(address account); + + function paused() public view returns (bool) { + return _paused; + } + /** + * @dev Emitted when the pause is lifted by a pauser (`account`). + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + constructor () { + _paused = false; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!_paused, "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + */ + modifier whenPaused() { + require(_paused, "Pausable: not paused"); + _; + } + + /** + * @dev Called to pause, triggers stopped state. + */ + function _pause() internal whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Called to unpause, returns to normal state. + */ + function _unpause() internal whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } +} + +// File: eth-contracts/contracts/libs/ownership/Ownable.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. + */ +abstract contract Ownable is Context, IOwnable { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor () { + 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; + } +} + +// File: eth-contracts/contracts/core/cross_chain_manager/data/EthCrossChainData.sol + + + + + +contract EthCrossChainData is IEthCrossChainData, Ownable, Pausable { + /* + Ethereum cross chain tx hash indexed by the automatically increased index. + This map exists for the reason that Poly chain can verify the existence of + cross chain request tx coming from Ethereum + */ + mapping(uint256 => bytes32) public EthToPolyTxHashMap; + // This index records the current Map length + uint256 public EthToPolyTxHashIndex; + + /* + When Poly chain switches the consensus epoch book keepers, the consensus peers public keys of Poly chain should be + changed into no-compressed version so that solidity smart contract can convert it to address type and + verify the signature derived from Poly chain account signature. + ConKeepersPkBytes means Consensus book Keepers Public Key Bytes + */ + bytes public ConKeepersPkBytes; + + // CurEpochStartHeight means Current Epoch Start Height of Poly chain block + uint32 public CurEpochStartHeight; + + // Record the from chain txs that have been processed + mapping(uint64 => mapping(bytes32 => bool)) FromChainTxExist; + + // Extra map for the usage of future potentially + mapping(bytes32 => mapping(bytes32 => bytes)) public ExtraData; + + // Store Current Epoch Start Height of Poly chain block + function putCurEpochStartHeight(uint32 curEpochStartHeight) public whenNotPaused onlyOwner returns (bool) { + CurEpochStartHeight = curEpochStartHeight; + return true; + } + + // Get Current Epoch Start Height of Poly chain block + function getCurEpochStartHeight() public view returns (uint32) { + return CurEpochStartHeight; + } + + // Store Consensus book Keepers Public Key Bytes + function putCurEpochConPubKeyBytes(bytes memory curEpochPkBytes) public whenNotPaused onlyOwner returns (bool) { + ConKeepersPkBytes = curEpochPkBytes; + return true; + } + + // Get Consensus book Keepers Public Key Bytes + function getCurEpochConPubKeyBytes() public view returns (bytes memory) { + return ConKeepersPkBytes; + } + + // Mark from chain tx fromChainTx as exist or processed + function markFromChainTxExist(uint64 fromChainId, bytes32 fromChainTx) public whenNotPaused onlyOwner returns (bool) { + FromChainTxExist[fromChainId][fromChainTx] = true; + return true; + } + + // Check if from chain tx fromChainTx has been processed before + function checkIfFromChainTxExist(uint64 fromChainId, bytes32 fromChainTx) public view returns (bool) { + return FromChainTxExist[fromChainId][fromChainTx]; + } + + // Get current recorded index of cross chain txs requesting from Ethereum to other public chains + // in order to help cross chain manager contract differenciate two cross chain tx requests + function getEthTxHashIndex() public view returns (uint256) { + return EthToPolyTxHashIndex; + } + + // Store Ethereum cross chain tx hash, increase the index record by 1 + function putEthTxHash(bytes32 ethTxHash) public whenNotPaused onlyOwner returns (bool) { + EthToPolyTxHashMap[EthToPolyTxHashIndex] = ethTxHash; + EthToPolyTxHashIndex = EthToPolyTxHashIndex + 1; + return true; + } + + // Get Ethereum cross chain tx hash indexed by ethTxHashIndex + function getEthTxHash(uint256 ethTxHashIndex) public view returns (bytes32) { + return EthToPolyTxHashMap[ethTxHashIndex]; + } + + // Store extra data, which may be used in the future + function putExtraData(bytes32 key1, bytes32 key2, bytes memory value) public whenNotPaused onlyOwner returns (bool) { + ExtraData[key1][key2] = value; + return true; + } + // Get extra data, which may be used in the future + function getExtraData(bytes32 key1, bytes32 key2) public view returns (bytes memory) { + return ExtraData[key1][key2]; + } + + function pause() onlyOwner whenNotPaused public returns (bool) { + _pause(); + return true; + } + + function unpause() onlyOwner whenPaused public returns (bool) { + _unpause(); + return true; + } +} diff --git a/smart-contracts/contracts/zilbridge/1/lockProxy.sol b/smart-contracts/contracts/zilbridge/1/lockProxy.sol new file mode 100644 index 0000000..9be320b --- /dev/null +++ b/smart-contracts/contracts/zilbridge/1/lockProxy.sol @@ -0,0 +1,1884 @@ +/** + *Submitted for verification at Etherscan.io on 2020-11-09 +*/ + +// File: contracts/libs/common/ZeroCopySource.sol + +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +/** + * @dev Wrappers over decoding and deserialization operation from bytes into bassic types in Solidity for PolyNetwork cross chain utility. + * + * Decode into basic types in Solidity from bytes easily. It's designed to be used + * for PolyNetwork cross chain application, and the decoding rules on Ethereum chain + * and the encoding rule on other chains should be consistent, and . Here we + * follow the underlying deserialization rule with implementation found here: + * https://github.com/polynetwork/poly/blob/master/common/zero_copy_source.go + * + * Using this library instead of the unchecked serialization method can help reduce + * the risk of serious bugs and handfule, so it's recommended to use it. + * + * Please note that risk can be minimized, yet not eliminated. + */ +library ZeroCopySource { + /* @notice Read next byte as boolean type starting at offset from buff + * @param buff Source bytes array + * @param offset The position from where we read the boolean value + * @return The the read boolean value and new offset + */ + function NextBool(bytes memory buff, uint256 offset) internal pure returns(bool, uint256) { + require(offset + 1 <= buff.length && offset < offset + 1, "Offset exceeds limit"); + // byte === bytes1 + bytes1 v; + assembly{ + v := mload(add(add(buff, 0x20), offset)) + } + bool value; + if (v == 0x01) { + value = true; + } else if (v == 0x00) { + value = false; + } else { + revert("NextBool value error"); + } + return (value, offset + 1); + } + + /* @notice Read next byte starting at offset from buff + * @param buff Source bytes array + * @param offset The position from where we read the byte value + * @return The read byte value and new offset + */ + function NextByte(bytes memory buff, uint256 offset) internal pure returns (bytes1, uint256) { + require(offset + 1 <= buff.length && offset < offset + 1, "NextByte, Offset exceeds maximum"); + bytes1 v; + assembly{ + v := mload(add(add(buff, 0x20), offset)) + } + return (v, offset + 1); + } + + /* @notice Read next byte as uint8 starting at offset from buff + * @param buff Source bytes array + * @param offset The position from where we read the byte value + * @return The read uint8 value and new offset + */ + function NextUint8(bytes memory buff, uint256 offset) internal pure returns (uint8, uint256) { + require(offset + 1 <= buff.length && offset < offset + 1, "NextUint8, Offset exceeds maximum"); + uint8 v; + assembly{ + let tmpbytes := mload(0x40) + let bvalue := mload(add(add(buff, 0x20), offset)) + mstore8(tmpbytes, byte(0, bvalue)) + mstore(0x40, add(tmpbytes, 0x01)) + v := mload(sub(tmpbytes, 0x1f)) + } + return (v, offset + 1); + } + + /* @notice Read next two bytes as uint16 type starting from offset + * @param buff Source bytes array + * @param offset The position from where we read the uint16 value + * @return The read uint16 value and updated offset + */ + function NextUint16(bytes memory buff, uint256 offset) internal pure returns (uint16, uint256) { + require(offset + 2 <= buff.length && offset < offset + 2, "NextUint16, offset exceeds maximum"); + + uint16 v; + assembly { + let tmpbytes := mload(0x40) + let bvalue := mload(add(add(buff, 0x20), offset)) + mstore8(tmpbytes, byte(0x01, bvalue)) + mstore8(add(tmpbytes, 0x01), byte(0, bvalue)) + mstore(0x40, add(tmpbytes, 0x02)) + v := mload(sub(tmpbytes, 0x1e)) + } + return (v, offset + 2); + } + + + /* @notice Read next four bytes as uint32 type starting from offset + * @param buff Source bytes array + * @param offset The position from where we read the uint32 value + * @return The read uint32 value and updated offset + */ + function NextUint32(bytes memory buff, uint256 offset) internal pure returns (uint32, uint256) { + require(offset + 4 <= buff.length && offset < offset + 4, "NextUint32, offset exceeds maximum"); + uint32 v; + assembly { + let tmpbytes := mload(0x40) + let byteLen := 0x04 + for { + let tindex := 0x00 + let bindex := sub(byteLen, 0x01) + let bvalue := mload(add(add(buff, 0x20), offset)) + } lt(tindex, byteLen) { + tindex := add(tindex, 0x01) + bindex := sub(bindex, 0x01) + }{ + mstore8(add(tmpbytes, tindex), byte(bindex, bvalue)) + } + mstore(0x40, add(tmpbytes, byteLen)) + v := mload(sub(tmpbytes, sub(0x20, byteLen))) + } + return (v, offset + 4); + } + + /* @notice Read next eight bytes as uint64 type starting from offset + * @param buff Source bytes array + * @param offset The position from where we read the uint64 value + * @return The read uint64 value and updated offset + */ + function NextUint64(bytes memory buff, uint256 offset) internal pure returns (uint64, uint256) { + require(offset + 8 <= buff.length && offset < offset + 8, "NextUint64, offset exceeds maximum"); + uint64 v; + assembly { + let tmpbytes := mload(0x40) + let byteLen := 0x08 + for { + let tindex := 0x00 + let bindex := sub(byteLen, 0x01) + let bvalue := mload(add(add(buff, 0x20), offset)) + } lt(tindex, byteLen) { + tindex := add(tindex, 0x01) + bindex := sub(bindex, 0x01) + }{ + mstore8(add(tmpbytes, tindex), byte(bindex, bvalue)) + } + mstore(0x40, add(tmpbytes, byteLen)) + v := mload(sub(tmpbytes, sub(0x20, byteLen))) + } + return (v, offset + 8); + } + + /* @notice Read next 32 bytes as uint256 type starting from offset, + there are limits considering the numerical limits in multi-chain + * @param buff Source bytes array + * @param offset The position from where we read the uint256 value + * @return The read uint256 value and updated offset + */ + function NextUint255(bytes memory buff, uint256 offset) internal pure returns (uint256, uint256) { + require(offset + 32 <= buff.length && offset < offset + 32, "NextUint255, offset exceeds maximum"); + uint256 v; + assembly { + let tmpbytes := mload(0x40) + let byteLen := 0x20 + for { + let tindex := 0x00 + let bindex := sub(byteLen, 0x01) + let bvalue := mload(add(add(buff, 0x20), offset)) + } lt(tindex, byteLen) { + tindex := add(tindex, 0x01) + bindex := sub(bindex, 0x01) + }{ + mstore8(add(tmpbytes, tindex), byte(bindex, bvalue)) + } + mstore(0x40, add(tmpbytes, byteLen)) + v := mload(tmpbytes) + } + require(v <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds the range"); + return (v, offset + 32); + } + /* @notice Read next variable bytes starting from offset, + the decoding rule coming from multi-chain + * @param buff Source bytes array + * @param offset The position from where we read the bytes value + * @return The read variable bytes array value and updated offset + */ + function NextVarBytes(bytes memory buff, uint256 offset) internal pure returns(bytes memory, uint256) { + uint len; + (len, offset) = NextVarUint(buff, offset); + require(offset + len <= buff.length && offset < offset + len, "NextVarBytes, offset exceeds maximum"); + bytes memory tempBytes; + assembly{ + switch iszero(len) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(len, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, len) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(buff, lengthmod), mul(0x20, iszero(lengthmod))), offset) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, len) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return (tempBytes, offset + len); + } + /* @notice Read next 32 bytes starting from offset, + * @param buff Source bytes array + * @param offset The position from where we read the bytes value + * @return The read bytes32 value and updated offset + */ + function NextHash(bytes memory buff, uint256 offset) internal pure returns (bytes32 , uint256) { + require(offset + 32 <= buff.length && offset < offset + 32, "NextHash, offset exceeds maximum"); + bytes32 v; + assembly { + v := mload(add(buff, add(offset, 0x20))) + } + return (v, offset + 32); + } + + /* @notice Read next 20 bytes starting from offset, + * @param buff Source bytes array + * @param offset The position from where we read the bytes value + * @return The read bytes20 value and updated offset + */ + function NextBytes20(bytes memory buff, uint256 offset) internal pure returns (bytes20 , uint256) { + require(offset + 20 <= buff.length && offset < offset + 20, "NextBytes20, offset exceeds maximum"); + bytes20 v; + assembly { + v := mload(add(buff, add(offset, 0x20))) + } + return (v, offset + 20); + } + + function NextVarUint(bytes memory buff, uint256 offset) internal pure returns(uint, uint256) { + bytes1 v; + (v, offset) = NextByte(buff, offset); + + uint value; + if (v == 0xFD) { + // return NextUint16(buff, offset); + (value, offset) = NextUint16(buff, offset); + require(value >= 0xFD && value <= 0xFFFF, "NextUint16, value outside range"); + return (value, offset); + } else if (v == 0xFE) { + // return NextUint32(buff, offset); + (value, offset) = NextUint32(buff, offset); + require(value > 0xFFFF && value <= 0xFFFFFFFF, "NextVarUint, value outside range"); + return (value, offset); + } else if (v == 0xFF) { + // return NextUint64(buff, offset); + (value, offset) = NextUint64(buff, offset); + require(value > 0xFFFFFFFF, "NextVarUint, value outside range"); + return (value, offset); + } else{ + // return (uint8(v), offset); + value = uint8(v); + require(value < 0xFD, "NextVarUint, value outside range"); + return (value, offset); + } + } +} + +// File: contracts/libs/common/ZeroCopySink.sol + + +/** + * @dev Wrappers over encoding and serialization operation into bytes from bassic types in Solidity for PolyNetwork cross chain utility. + * + * Encode basic types in Solidity into bytes easily. It's designed to be used + * for PolyNetwork cross chain application, and the encoding rules on Ethereum chain + * and the decoding rules on other chains should be consistent. Here we + * follow the underlying serialization rule with implementation found here: + * https://github.com/polynetwork/poly/blob/master/common/zero_copy_sink.go + * + * Using this library instead of the unchecked serialization method can help reduce + * the risk of serious bugs and handfule, so it's recommended to use it. + * + * Please note that risk can be minimized, yet not eliminated. + */ +library ZeroCopySink { + /* @notice Convert boolean value into bytes + * @param b The boolean value + * @return Converted bytes array + */ + function WriteBool(bool b) internal pure returns (bytes memory) { + bytes memory buff; + assembly{ + buff := mload(0x40) + mstore(buff, 1) + switch iszero(b) + case 1 { + mstore(add(buff, 0x20), shl(248, 0x00)) + // mstore8(add(buff, 0x20), 0x00) + } + default { + mstore(add(buff, 0x20), shl(248, 0x01)) + // mstore8(add(buff, 0x20), 0x01) + } + mstore(0x40, add(buff, 0x21)) + } + return buff; + } + + /* @notice Convert byte value into bytes + * @param b The byte value + * @return Converted bytes array + */ + function WriteByte(bytes1 b) internal pure returns (bytes memory) { + return WriteUint8(uint8(b)); + } + + /* @notice Convert uint8 value into bytes + * @param v The uint8 value + * @return Converted bytes array + */ + function WriteUint8(uint8 v) internal pure returns (bytes memory) { + bytes memory buff; + assembly{ + buff := mload(0x40) + mstore(buff, 1) + mstore(add(buff, 0x20), shl(248, v)) + // mstore(add(buff, 0x20), byte(0x1f, v)) + mstore(0x40, add(buff, 0x21)) + } + return buff; + } + + /* @notice Convert uint16 value into bytes + * @param v The uint16 value + * @return Converted bytes array + */ + function WriteUint16(uint16 v) internal pure returns (bytes memory) { + bytes memory buff; + + assembly{ + buff := mload(0x40) + let byteLen := 0x02 + mstore(buff, byteLen) + for { + let mindex := 0x00 + let vindex := 0x1f + } lt(mindex, byteLen) { + mindex := add(mindex, 0x01) + vindex := sub(vindex, 0x01) + }{ + mstore8(add(add(buff, 0x20), mindex), byte(vindex, v)) + } + mstore(0x40, add(buff, 0x22)) + } + return buff; + } + + /* @notice Convert uint32 value into bytes + * @param v The uint32 value + * @return Converted bytes array + */ + function WriteUint32(uint32 v) internal pure returns(bytes memory) { + bytes memory buff; + assembly{ + buff := mload(0x40) + let byteLen := 0x04 + mstore(buff, byteLen) + for { + let mindex := 0x00 + let vindex := 0x1f + } lt(mindex, byteLen) { + mindex := add(mindex, 0x01) + vindex := sub(vindex, 0x01) + }{ + mstore8(add(add(buff, 0x20), mindex), byte(vindex, v)) + } + mstore(0x40, add(buff, 0x24)) + } + return buff; + } + + /* @notice Convert uint64 value into bytes + * @param v The uint64 value + * @return Converted bytes array + */ + function WriteUint64(uint64 v) internal pure returns(bytes memory) { + bytes memory buff; + + assembly{ + buff := mload(0x40) + let byteLen := 0x08 + mstore(buff, byteLen) + for { + let mindex := 0x00 + let vindex := 0x1f + } lt(mindex, byteLen) { + mindex := add(mindex, 0x01) + vindex := sub(vindex, 0x01) + }{ + mstore8(add(add(buff, 0x20), mindex), byte(vindex, v)) + } + mstore(0x40, add(buff, 0x28)) + } + return buff; + } + + /* @notice Convert limited uint256 value into bytes + * @param v The uint256 value + * @return Converted bytes array + */ + function WriteUint255(uint256 v) internal pure returns (bytes memory) { + require(v <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds uint255 range"); + bytes memory buff; + + assembly{ + buff := mload(0x40) + let byteLen := 0x20 + mstore(buff, byteLen) + for { + let mindex := 0x00 + let vindex := 0x1f + } lt(mindex, byteLen) { + mindex := add(mindex, 0x01) + vindex := sub(vindex, 0x01) + }{ + mstore8(add(add(buff, 0x20), mindex), byte(vindex, v)) + } + mstore(0x40, add(buff, 0x40)) + } + return buff; + } + + /* @notice Encode bytes format data into bytes + * @param data The bytes array data + * @return Encoded bytes array + */ + function WriteVarBytes(bytes memory data) internal pure returns (bytes memory) { + uint64 l = uint64(data.length); + return abi.encodePacked(WriteVarUint(l), data); + } + + function WriteVarUint(uint64 v) internal pure returns (bytes memory) { + if (v < 0xFD){ + return WriteUint8(uint8(v)); + } else if (v <= 0xFFFF) { + return abi.encodePacked(WriteByte(0xFD), WriteUint16(uint16(v))); + } else if (v <= 0xFFFFFFFF) { + return abi.encodePacked(WriteByte(0xFE), WriteUint32(uint32(v))); + } else { + return abi.encodePacked(WriteByte(0xFF), WriteUint64(uint64(v))); + } + } +} + +// File: contracts/libs/utils/ReentrancyGuard.sol + + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + bool private _notEntered; + + constructor () { + // Storing an initial non-zero value makes deployment a bit more + // expensive, but in exchange the refund on every call to nonReentrant + // will be lower in amount. Since refunds are capped to a percetange of + // the total transaction's gas, it is best to keep them low in cases + // like this one, to increase the likelihood of the full refund coming + // into effect. + _notEntered = true; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_notEntered, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _notEntered = false; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _notEntered = true; + } +} + +// File: contracts/libs/utils/Utils.sol + + +library Utils { + + /* @notice Convert the bytes array to bytes32 type, the bytes array length must be 32 + * @param _bs Source bytes array + * @return bytes32 + */ + function bytesToBytes32(bytes memory _bs) internal pure returns (bytes32 value) { + require(_bs.length == 32, "bytes length is not 32."); + assembly { + // load 32 bytes from memory starting from position _bs + 0x20 since the first 0x20 bytes stores _bs length + value := mload(add(_bs, 0x20)) + } + } + + /* @notice Convert bytes to uint256 + * @param _b Source bytes should have length of 32 + * @return uint256 + */ + function bytesToUint256(bytes memory _bs) internal pure returns (uint256 value) { + require(_bs.length == 32, "bytes length is not 32."); + assembly { + // load 32 bytes from memory starting from position _bs + 32 + value := mload(add(_bs, 0x20)) + } + require(value <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds the range"); + } + + /* @notice Convert uint256 to bytes + * @param _b uint256 that needs to be converted + * @return bytes + */ + function uint256ToBytes(uint256 _value) internal pure returns (bytes memory bs) { + require(_value <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds the range"); + assembly { + // Get a location of some free memory and store it in result as + // Solidity does for memory variables. + bs := mload(0x40) + // Put 0x20 at the first word, the length of bytes for uint256 value + mstore(bs, 0x20) + //In the next word, put value in bytes format to the next 32 bytes + mstore(add(bs, 0x20), _value) + // Update the free-memory pointer by padding our last write location to 32 bytes + mstore(0x40, add(bs, 0x40)) + } + } + + /* @notice Convert bytes to address + * @param _bs Source bytes: bytes length must be 20 + * @return Converted address from source bytes + */ + function bytesToAddress(bytes memory _bs) internal pure returns (address addr) + { + require(_bs.length == 20, "bytes length does not match address"); + assembly { + // for _bs, first word store _bs.length, second word store _bs.value + // load 32 bytes from mem[_bs+20], convert it into Uint160, meaning we take last 20 bytes as addr (address). + addr := mload(add(_bs, 0x14)) + } + + } + + /* @notice Convert address to bytes + * @param _addr Address need to be converted + * @return Converted bytes from address + */ + function addressToBytes(address _addr) internal pure returns (bytes memory bs){ + assembly { + // Get a location of some free memory and store it in result as + // Solidity does for memory variables. + bs := mload(0x40) + // Put 20 (address byte length) at the first word, the length of bytes for uint256 value + mstore(bs, 0x14) + // logical shift left _a by 12 bytes, change _a from right-aligned to left-aligned + mstore(add(bs, 0x20), shl(96, _addr)) + // Update the free-memory pointer by padding our last write location to 32 bytes + mstore(0x40, add(bs, 0x40)) + } + } + + /* @notice Do hash leaf as the multi-chain does + * @param _data Data in bytes format + * @return Hashed value in bytes32 format + */ + function hashLeaf(bytes memory _data) internal pure returns (bytes32 result) { + result = sha256(abi.encodePacked(bytes1(0x0), _data)); + } + + /* @notice Do hash children as the multi-chain does + * @param _l Left node + * @param _r Right node + * @return Hashed value in bytes32 format + */ + function hashChildren(bytes32 _l, bytes32 _r) internal pure returns (bytes32 result) { + result = sha256(abi.encodePacked(bytes1(0x01), _l, _r)); + } + + /* @notice Compare if two bytes are equal, which are in storage and memory, seperately + Refer from https://github.com/summa-tx/bitcoin-spv/blob/master/solidity/contracts/BytesLib.sol#L368 + * @param _preBytes The bytes stored in storage + * @param _postBytes The bytes stored in memory + * @return Bool type indicating if they are equal + */ + function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) { + bool success = true; + + assembly { + // we know _preBytes_offset is 0 + let fslot := sload(_preBytes.slot) + // Arrays of 31 bytes or less have an even value in their slot, + // while longer arrays have an odd value. The actual length is + // the slot divided by two for odd values, and the lowest order + // byte divided by two for even values. + // If the slot is even, bitwise and the slot with 255 and divide by + // two to get the length. If the slot is odd, bitwise and the slot + // with -1 and divide by two. + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) + let mlength := mload(_postBytes) + + // if lengths don't match the arrays are not equal + switch eq(slength, mlength) + case 1 { + // fslot can contain both the length and contents of the array + // if slength < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + // slength != 0 + if iszero(iszero(slength)) { + switch lt(slength, 32) + case 1 { + // blank the last byte which is the length + fslot := mul(div(fslot, 0x100), 0x100) + + if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { + // unsuccess: + success := 0 + } + } + default { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + let sc := keccak256(0x0, 0x20) + + let mc := add(_postBytes, 0x20) + let end := add(mc, mlength) + + // the next line is the loop condition: + // while(uint(mc < end) + cb == 2) + for {} eq(add(lt(mc, end), cb), 2) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + if iszero(eq(sload(sc), mload(mc))) { + // unsuccess: + success := 0 + cb := 0 + } + } + } + } + } + default { + // unsuccess: + success := 0 + } + } + + return success; + } + + /* @notice Slice the _bytes from _start index till the result has length of _length + Refer from https://github.com/summa-tx/bitcoin-spv/blob/master/solidity/contracts/BytesLib.sol#L246 + * @param _bytes The original bytes needs to be sliced + * @param _start The index of _bytes for the start of sliced bytes + * @param _length The index of _bytes for the end of sliced bytes + * @return The sliced bytes + */ + function slice( + bytes memory _bytes, + uint _start, + uint _length + ) + internal + pure + returns (bytes memory) + { + require(_bytes.length >= (_start + _length)); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + // lengthmod <= _length % 32 + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + /* @notice Check if the elements number of _signers within _keepers array is no less than _m + * @param _keepers The array consists of serveral address + * @param _signers Some specific addresses to be looked into + * @param _m The number requirement paramter + * @return True means containment, false meansdo do not contain. + */ + function containMAddresses(address[] memory _keepers, address[] memory _signers, uint _m) internal pure returns (bool){ + uint m = 0; + for(uint i = 0; i < _signers.length; i++){ + for (uint j = 0; j < _keepers.length; j++) { + if (_signers[i] == _keepers[j]) { + m++; + delete _keepers[j]; + } + } + } + return m >= _m; + } + + /* @notice TODO + * @param key + * @return + */ + function compressMCPubKey(bytes memory key) internal pure returns (bytes memory newkey) { + require(key.length >= 67, "key lenggh is too short"); + newkey = slice(key, 0, 35); + if (uint8(key[66]) % 2 == 0){ + newkey[2] = bytes1(0x02); + } else { + newkey[2] = bytes1(0x03); + } + return newkey; + } + + /** + * @dev Returns true if `account` is a contract. + * Refer from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L18 + * + * This test is non-exhaustive, and there may be false-negatives: during the + * execution of a contract's constructor, its address will be reported as + * not containing a contract. + * + * IMPORTANT: It is unsafe to assume that an address for which this + * function returns false is an externally-owned account (EOA) and not a + * contract. + */ + function isContract(address account) internal view returns (bool) { + // This method relies in extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != 0x0 && codehash != accountHash); + } +} + +// File: contracts/libs/math/SafeMath.sol + + + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + // 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. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +// File: contracts/Wallet.sol + + + +interface ERC20 { + function approve(address spender, uint256 amount) external returns (bool); + function transfer(address recipient, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + function balanceOf(address account) external view returns (uint256); +} + +/// @title The Wallet contract for Switcheo TradeHub +/// @author Switcheo Network +/// @notice This contract faciliates deposits for Switcheo TradeHub. +/// @dev This contract is used together with the LockProxy contract to allow users +/// to deposit funds without requiring them to have ETH +contract Wallet { + bool public isInitialized; + address public creator; + address public owner; + bytes public swthAddress; + + function initialize(address _owner, bytes calldata _swthAddress) external { + require(isInitialized == false, "Contract already initialized"); + isInitialized = true; + creator = msg.sender; + owner = _owner; + swthAddress = _swthAddress; + } + + /// @dev Allow this contract to receive Ethereum + receive() external payable {} + + /// @dev Allow this contract to receive ERC223 tokens + // An empty implementation is required so that the ERC223 token will not + // throw an error on transfer + function tokenFallback(address, uint, bytes calldata) external {} + + /// @dev send ETH from this contract to its creator + function sendETHToCreator(uint256 _amount) external { + require(msg.sender == creator, "Sender must be creator"); + // we use `call` here following the recommendation from + // https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/ + (bool success, ) = creator.call{value: _amount}(""); + require(success, "Transfer failed"); + } + + /// @dev send tokens from this contract to its creator + function sendERC20ToCreator(address _assetId, uint256 _amount) external { + require(msg.sender == creator, "Sender must be creator"); + + ERC20 token = ERC20(_assetId); + _callOptionalReturn( + token, + abi.encodeWithSelector( + token.transfer.selector, + creator, + _amount + ) + ); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(ERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. + + // A Solidity high level call has three parts: + // 1. The target address is checked to verify it contains contract code + // 2. The call itself is made, and success asserted + // 3. The return value is decoded, which in turn checks the size of the returned data. + // solhint-disable-next-line max-line-length + require(_isContract(address(token)), "SafeERC20: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = address(token).call(data); + require(success, "SafeERC20: low-level call failed"); + + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } + + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `_isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function _isContract(address account) private view returns (bool) { + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != accountHash && codehash != 0x0); + } +} + +// File: contracts/LockProxy.sol + + +interface CCM { + function crossChain(uint64 _toChainId, bytes calldata _toContract, bytes calldata _method, bytes calldata _txData) external returns (bool); +} + +interface CCMProxy { + function getEthCrossChainManager() external view returns (address); +} + +/// @title The LockProxy contract for Switcheo TradeHub +/// @author Switcheo Network +/// @notice This contract faciliates deposits and withdrawals to Switcheo TradeHub. +/// @dev The contract also allows for additional features in the future through "extension" contracts. +contract LockProxy is ReentrancyGuard { + using SafeMath for uint256; + + // used for cross-chain addExtension and removeExtension methods + struct ExtensionTxArgs { + bytes extensionAddress; + } + + // used for cross-chain registerAsset method + struct RegisterAssetTxArgs { + bytes assetHash; + bytes nativeAssetHash; + } + + // used for cross-chain lock and unlock methods + struct TransferTxArgs { + bytes fromAssetHash; + bytes toAssetHash; + bytes toAddress; + uint256 amount; + uint256 feeAmount; + bytes feeAddress; + bytes fromAddress; + uint256 nonce; + } + + // used to create a unique salt for wallet creation + bytes public constant SALT_PREFIX = "switcheo-eth-wallet-factory-v1"; + address public constant ETH_ASSET_HASH = address(0); + + CCMProxy public ccmProxy; + uint64 public counterpartChainId; + uint256 public currentNonce = 0; + + // a mapping of assetHashes to the hash of + // (associated proxy address on Switcheo TradeHub, associated asset hash on Switcheo TradeHub) + mapping(address => bytes32) public registry; + + // a record of signed messages to prevent replay attacks + mapping(bytes32 => bool) public seenMessages; + + // a mapping of extension contracts + mapping(address => bool) public extensions; + + // a record of created wallets + mapping(address => bool) public wallets; + + event LockEvent( + address fromAssetHash, + address fromAddress, + uint64 toChainId, + bytes toAssetHash, + bytes toAddress, + bytes txArgs + ); + + event UnlockEvent( + address toAssetHash, + address toAddress, + uint256 amount, + bytes txArgs + ); + + constructor(address _ccmProxyAddress, uint64 _counterpartChainId) public { + require(_counterpartChainId > 0, "counterpartChainId cannot be zero"); + require(_ccmProxyAddress != address(0), "ccmProxyAddress cannot be empty"); + counterpartChainId = _counterpartChainId; + ccmProxy = CCMProxy(_ccmProxyAddress); + } + + modifier onlyManagerContract() { + require( + msg.sender == ccmProxy.getEthCrossChainManager(), + "msg.sender is not CCM" + ); + _; + } + + /// @dev Allow this contract to receive Ethereum + receive() external payable {} + + /// @dev Allow this contract to receive ERC223 tokens + /// An empty implementation is required so that the ERC223 token will not + /// throw an error on transfer, this is specific to ERC223 tokens which + /// require this implementation, e.g. DGTX + function tokenFallback(address, uint, bytes calldata) external {} + + /// @dev Calculate the wallet address for the given owner and Switcheo TradeHub address + /// @param _ownerAddress the Ethereum address which the user has control over, i.e. can sign msgs with + /// @param _swthAddress the hex value of the user's Switcheo TradeHub address + /// @param _bytecodeHash the hash of the wallet contract's bytecode + /// @return the wallet address + function getWalletAddress( + address _ownerAddress, + bytes calldata _swthAddress, + bytes32 _bytecodeHash + ) + external + view + returns (address) + { + bytes32 salt = _getSalt( + _ownerAddress, + _swthAddress + ); + + bytes32 data = keccak256( + abi.encodePacked(bytes1(0xff), address(this), salt, _bytecodeHash) + ); + + return address(bytes20(data << 96)); + } + + /// @dev Create the wallet for the given owner and Switcheo TradeHub address + /// @param _ownerAddress the Ethereum address which the user has control over, i.e. can sign msgs with + /// @param _swthAddress the hex value of the user's Switcheo TradeHub address + /// @return true if success + function createWallet( + address _ownerAddress, + bytes calldata _swthAddress + ) + external + nonReentrant + returns (bool) + { + require(_ownerAddress != address(0), "Empty ownerAddress"); + require(_swthAddress.length != 0, "Empty swthAddress"); + + bytes32 salt = _getSalt( + _ownerAddress, + _swthAddress + ); + + Wallet wallet = new Wallet{salt: salt}(); + wallet.initialize(_ownerAddress, _swthAddress); + wallets[address(wallet)] = true; + + return true; + } + + /// @dev Add a contract as an extension + /// @param _argsBz the serialized ExtensionTxArgs + /// @param _fromChainId the originating chainId + /// @return true if success + function addExtension( + bytes calldata _argsBz, + bytes calldata /* _fromContractAddr */, + uint64 _fromChainId + ) + external + onlyManagerContract + nonReentrant + returns (bool) + { + require(_fromChainId == counterpartChainId, "Invalid chain ID"); + + ExtensionTxArgs memory args = _deserializeExtensionTxArgs(_argsBz); + address extensionAddress = Utils.bytesToAddress(args.extensionAddress); + extensions[extensionAddress] = true; + + return true; + } + + /// @dev Remove a contract from the extensions mapping + /// @param _argsBz the serialized ExtensionTxArgs + /// @param _fromChainId the originating chainId + /// @return true if success + function removeExtension( + bytes calldata _argsBz, + bytes calldata /* _fromContractAddr */, + uint64 _fromChainId + ) + external + onlyManagerContract + nonReentrant + returns (bool) + { + require(_fromChainId == counterpartChainId, "Invalid chain ID"); + + ExtensionTxArgs memory args = _deserializeExtensionTxArgs(_argsBz); + address extensionAddress = Utils.bytesToAddress(args.extensionAddress); + extensions[extensionAddress] = false; + + return true; + } + + /// @dev Marks an asset as registered by mapping the asset's address to + /// the specified _fromContractAddr and assetHash on Switcheo TradeHub + /// @param _argsBz the serialized RegisterAssetTxArgs + /// @param _fromContractAddr the associated contract address on Switcheo TradeHub + /// @param _fromChainId the originating chainId + /// @return true if success + function registerAsset( + bytes calldata _argsBz, + bytes calldata _fromContractAddr, + uint64 _fromChainId + ) + external + onlyManagerContract + nonReentrant + returns (bool) + { + require(_fromChainId == counterpartChainId, "Invalid chain ID"); + + RegisterAssetTxArgs memory args = _deserializeRegisterAssetTxArgs(_argsBz); + _markAssetAsRegistered( + Utils.bytesToAddress(args.nativeAssetHash), + _fromContractAddr, + args.assetHash + ); + + return true; + } + + /// @dev Performs a deposit from a Wallet contract + /// @param _walletAddress address of the wallet contract, the wallet contract + /// does not receive ETH in this call, but _walletAddress still needs to be payable + /// since the wallet contract can receive ETH, there would be compile errors otherwise + /// @param _assetHash the asset to deposit + /// @param _targetProxyHash the associated proxy hash on Switcheo TradeHub + /// @param _toAssetHash the associated asset hash on Switcheo TradeHub + /// @param _feeAddress the hex version of the Switcheo TradeHub address to send the fee to + /// @param _values[0]: amount, the number of tokens to deposit + /// @param _values[1]: feeAmount, the number of tokens to be used as fees + /// @param _values[2]: nonce, to prevent replay attacks + /// @param _values[3]: callAmount, some tokens may burn an amount before transfer + /// so we allow a callAmount to support these tokens + /// @param _v: the v value of the wallet owner's signature + /// @param _rs: the r, s values of the wallet owner's signature + function lockFromWallet( + address payable _walletAddress, + address _assetHash, + bytes calldata _targetProxyHash, + bytes calldata _toAssetHash, + bytes calldata _feeAddress, + uint256[] calldata _values, + uint8 _v, + bytes32[] calldata _rs + ) + external + nonReentrant + returns (bool) + { + require(wallets[_walletAddress], "Invalid wallet address"); + + Wallet wallet = Wallet(_walletAddress); + _validateLockFromWallet( + wallet.owner(), + _assetHash, + _targetProxyHash, + _toAssetHash, + _feeAddress, + _values, + _v, + _rs + ); + + // it is very important that this function validates the success of a transfer correctly + // since, once this line is passed, the deposit is assumed to be successful + // which will eventually result in the specified amount of tokens being minted for the + // wallet.swthAddress on Switcheo TradeHub + _transferInFromWallet(_walletAddress, _assetHash, _values[0], _values[3]); + + _lock( + _assetHash, + _targetProxyHash, + _toAssetHash, + wallet.swthAddress(), + _values[0], + _values[1], + _feeAddress + ); + + return true; + } + + /// @dev Performs a deposit + /// @param _assetHash the asset to deposit + /// @param _targetProxyHash the associated proxy hash on Switcheo TradeHub + /// @param _toAddress the hex version of the Switcheo TradeHub address to deposit to + /// @param _toAssetHash the associated asset hash on Switcheo TradeHub + /// @param _feeAddress the hex version of the Switcheo TradeHub address to send the fee to + /// @param _values[0]: amount, the number of tokens to deposit + /// @param _values[1]: feeAmount, the number of tokens to be used as fees + /// @param _values[2]: callAmount, some tokens may burn an amount before transfer + /// so we allow a callAmount to support these tokens + function lock( + address _assetHash, + bytes calldata _targetProxyHash, + bytes calldata _toAddress, + bytes calldata _toAssetHash, + bytes calldata _feeAddress, + uint256[] calldata _values + ) + external + payable + nonReentrant + returns (bool) + { + + // it is very important that this function validates the success of a transfer correctly + // since, once this line is passed, the deposit is assumed to be successful + // which will eventually result in the specified amount of tokens being minted for the + // _toAddress on Switcheo TradeHub + _transferIn(_assetHash, _values[0], _values[2]); + + _lock( + _assetHash, + _targetProxyHash, + _toAssetHash, + _toAddress, + _values[0], + _values[1], + _feeAddress + ); + + return true; + } + + /// @dev Performs a withdrawal that was initiated on Switcheo TradeHub + /// @param _argsBz the serialized TransferTxArgs + /// @param _fromContractAddr the associated contract address on Switcheo TradeHub + /// @param _fromChainId the originating chainId + /// @return true if success + function unlock( + bytes calldata _argsBz, + bytes calldata _fromContractAddr, + uint64 _fromChainId + ) + external + onlyManagerContract + nonReentrant + returns (bool) + { + require(_fromChainId == counterpartChainId, "Invalid chain ID"); + + TransferTxArgs memory args = _deserializeTransferTxArgs(_argsBz); + require(args.fromAssetHash.length > 0, "Invalid fromAssetHash"); + require(args.toAssetHash.length == 20, "Invalid toAssetHash"); + + address toAssetHash = Utils.bytesToAddress(args.toAssetHash); + address toAddress = Utils.bytesToAddress(args.toAddress); + + _validateAssetRegistration(toAssetHash, _fromContractAddr, args.fromAssetHash); + _transferOut(toAddress, toAssetHash, args.amount); + + emit UnlockEvent(toAssetHash, toAddress, args.amount, _argsBz); + return true; + } + + /// @dev Performs a transfer of funds, this is only callable by approved extension contracts + /// the `nonReentrant` guard is intentionally not added to this function, to allow for more flexibility. + /// The calling contract should be secure and have its own `nonReentrant` guard as needed. + /// @param _receivingAddress the address to transfer to + /// @param _assetHash the asset to transfer + /// @param _amount the amount to transfer + /// @return true if success + function extensionTransfer( + address _receivingAddress, + address _assetHash, + uint256 _amount + ) + external + returns (bool) + { + require( + extensions[msg.sender] == true, + "Invalid extension" + ); + + if (_assetHash == ETH_ASSET_HASH) { + // we use `call` here since the _receivingAddress could be a contract + // see https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/ + // for more info + (bool success, ) = _receivingAddress.call{value: _amount}(""); + require(success, "Transfer failed"); + return true; + } + + ERC20 token = ERC20(_assetHash); + _callOptionalReturn( + token, + abi.encodeWithSelector( + token.approve.selector, + _receivingAddress, + _amount + ) + ); + + return true; + } + + /// @dev Marks an asset as registered by associating it to a specified Switcheo TradeHub proxy and asset hash + /// @param _assetHash the address of the asset to mark + /// @param _proxyAddress the associated proxy address on Switcheo TradeHub + /// @param _toAssetHash the associated asset hash on Switcheo TradeHub + function _markAssetAsRegistered( + address _assetHash, + bytes memory _proxyAddress, + bytes memory _toAssetHash + ) + private + { + require(_proxyAddress.length == 20, "Invalid proxyAddress"); + require( + registry[_assetHash] == bytes32(0), + "Asset already registered" + ); + + bytes32 value = keccak256(abi.encodePacked( + _proxyAddress, + _toAssetHash + )); + + registry[_assetHash] = value; + } + + /// @dev Validates that an asset's registration matches the given params + /// @param _assetHash the address of the asset to check + /// @param _proxyAddress the expected proxy address on Switcheo TradeHub + /// @param _toAssetHash the expected asset hash on Switcheo TradeHub + function _validateAssetRegistration( + address _assetHash, + bytes memory _proxyAddress, + bytes memory _toAssetHash + ) + private + view + { + require(_proxyAddress.length == 20, "Invalid proxyAddress"); + bytes32 value = keccak256(abi.encodePacked( + _proxyAddress, + _toAssetHash + )); + require(registry[_assetHash] == value, "Asset not registered"); + } + + /// @dev validates the asset registration and calls the CCM contract + function _lock( + address _fromAssetHash, + bytes memory _targetProxyHash, + bytes memory _toAssetHash, + bytes memory _toAddress, + uint256 _amount, + uint256 _feeAmount, + bytes memory _feeAddress + ) + private + { + require(_targetProxyHash.length == 20, "Invalid targetProxyHash"); + require(_toAssetHash.length > 0, "Empty toAssetHash"); + require(_toAddress.length > 0, "Empty toAddress"); + require(_amount > 0, "Amount must be more than zero"); + require(_feeAmount < _amount, "Fee amount cannot be greater than amount"); + + _validateAssetRegistration(_fromAssetHash, _targetProxyHash, _toAssetHash); + + TransferTxArgs memory txArgs = TransferTxArgs({ + fromAssetHash: Utils.addressToBytes(_fromAssetHash), + toAssetHash: _toAssetHash, + toAddress: _toAddress, + amount: _amount, + feeAmount: _feeAmount, + feeAddress: _feeAddress, + fromAddress: abi.encodePacked(msg.sender), + nonce: _getNextNonce() + }); + + bytes memory txData = _serializeTransferTxArgs(txArgs); + CCM ccm = _getCcm(); + require( + ccm.crossChain(counterpartChainId, _targetProxyHash, "unlock", txData), + "EthCrossChainManager crossChain executed error!" + ); + + emit LockEvent(_fromAssetHash, msg.sender, counterpartChainId, _toAssetHash, _toAddress, txData); + } + + /// @dev validate the signature for lockFromWallet + function _validateLockFromWallet( + address _walletOwner, + address _assetHash, + bytes memory _targetProxyHash, + bytes memory _toAssetHash, + bytes memory _feeAddress, + uint256[] memory _values, + uint8 _v, + bytes32[] memory _rs + ) + private + { + bytes32 message = keccak256(abi.encodePacked( + "sendTokens", + _assetHash, + _targetProxyHash, + _toAssetHash, + _feeAddress, + _values[0], + _values[1], + _values[2] + )); + + require(seenMessages[message] == false, "Message already seen"); + seenMessages[message] = true; + _validateSignature(message, _walletOwner, _v, _rs[0], _rs[1]); + } + + /// @dev transfers funds from a Wallet contract into this contract + /// the difference between this contract's before and after balance must equal _amount + /// this is assumed to be sufficient in ensuring that the expected amount + /// of funds were transferred in + function _transferInFromWallet( + address payable _walletAddress, + address _assetHash, + uint256 _amount, + uint256 _callAmount + ) + private + { + Wallet wallet = Wallet(_walletAddress); + if (_assetHash == ETH_ASSET_HASH) { + uint256 before = address(this).balance; + + wallet.sendETHToCreator(_callAmount); + + uint256 transferred = address(this).balance.sub(before); + require(transferred == _amount, "ETH transferred does not match the expected amount"); + return; + } + + ERC20 token = ERC20(_assetHash); + { + uint256 before = token.balanceOf(address(this)); + + wallet.sendERC20ToCreator(_assetHash, _callAmount); + + uint256 transferred = token.balanceOf(address(this)).sub(before); + require(transferred == _amount, "Tokens transferred does not match the expected amount"); + } + } + + /// @dev transfers funds from an address into this contract + /// for ETH transfers, we only check that msg.value == _amount, and _callAmount is ignored + /// for token transfers, the difference between this contract's before and after balance must equal _amount + /// these checks are assumed to be sufficient in ensuring that the expected amount + /// of funds were transferred in + function _transferIn( + address _assetHash, + uint256 _amount, + uint256 _callAmount + ) + private + { + if (_assetHash == ETH_ASSET_HASH) { + require(msg.value == _amount, "ETH transferred does not match the expected amount"); + return; + } + + ERC20 token = ERC20(_assetHash); + uint256 before = token.balanceOf(address(this)); + _callOptionalReturn( + token, + abi.encodeWithSelector( + token.transferFrom.selector, + msg.sender, + address(this), + _callAmount + ) + ); + uint256 transferred = token.balanceOf(address(this)).sub(before); + require(transferred == _amount, "Tokens transferred does not match the expected amount"); + } + + /// @dev transfers funds from this contract to the _toAddress + function _transferOut( + address _toAddress, + address _assetHash, + uint256 _amount + ) + private + { + if (_assetHash == ETH_ASSET_HASH) { + // we use `call` here since the _receivingAddress could be a contract + // see https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/ + // for more info + (bool success, ) = _toAddress.call{value: _amount}(""); + require(success, "Transfer failed"); + return; + } + + ERC20 token = ERC20(_assetHash); + _callOptionalReturn( + token, + abi.encodeWithSelector( + token.transfer.selector, + _toAddress, + _amount + ) + ); + } + + /// @dev validates a signature against the specified user address + function _validateSignature( + bytes32 _message, + address _user, + uint8 _v, + bytes32 _r, + bytes32 _s + ) + private + pure + { + bytes32 prefixedMessage = keccak256(abi.encodePacked( + "\x19Ethereum Signed Message:\n32", + _message + )); + + require( + _user == ecrecover(prefixedMessage, _v, _r, _s), + "Invalid signature" + ); + } + + function _serializeTransferTxArgs(TransferTxArgs memory args) private pure returns (bytes memory) { + bytes memory buff; + buff = abi.encodePacked( + ZeroCopySink.WriteVarBytes(args.fromAssetHash), + ZeroCopySink.WriteVarBytes(args.toAssetHash), + ZeroCopySink.WriteVarBytes(args.toAddress), + ZeroCopySink.WriteUint255(args.amount), + ZeroCopySink.WriteUint255(args.feeAmount), + ZeroCopySink.WriteVarBytes(args.feeAddress), + ZeroCopySink.WriteVarBytes(args.fromAddress), + ZeroCopySink.WriteUint255(args.nonce) + ); + return buff; + } + + function _deserializeTransferTxArgs(bytes memory valueBz) private pure returns (TransferTxArgs memory) { + TransferTxArgs memory args; + uint256 off = 0; + (args.fromAssetHash, off) = ZeroCopySource.NextVarBytes(valueBz, off); + (args.toAssetHash, off) = ZeroCopySource.NextVarBytes(valueBz, off); + (args.toAddress, off) = ZeroCopySource.NextVarBytes(valueBz, off); + (args.amount, off) = ZeroCopySource.NextUint255(valueBz, off); + return args; + } + + function _deserializeRegisterAssetTxArgs(bytes memory valueBz) private pure returns (RegisterAssetTxArgs memory) { + RegisterAssetTxArgs memory args; + uint256 off = 0; + (args.assetHash, off) = ZeroCopySource.NextVarBytes(valueBz, off); + (args.nativeAssetHash, off) = ZeroCopySource.NextVarBytes(valueBz, off); + return args; + } + + function _deserializeExtensionTxArgs(bytes memory valueBz) private pure returns (ExtensionTxArgs memory) { + ExtensionTxArgs memory args; + uint256 off = 0; + (args.extensionAddress, off) = ZeroCopySource.NextVarBytes(valueBz, off); + return args; + } + + function _getCcm() private view returns (CCM) { + CCM ccm = CCM(ccmProxy.getEthCrossChainManager()); + return ccm; + } + + function _getNextNonce() private returns (uint256) { + currentNonce = currentNonce.add(1); + return currentNonce; + } + + function _getSalt( + address _ownerAddress, + bytes memory _swthAddress + ) + private + pure + returns (bytes32) + { + return keccak256(abi.encodePacked( + SALT_PREFIX, + _ownerAddress, + _swthAddress + )); + } + + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(ERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. + + // A Solidity high level call has three parts: + // 1. The target address is checked to verify it contains contract code + // 2. The call itself is made, and success asserted + // 3. The return value is decoded, which in turn checks the size of the returned data. + // solhint-disable-next-line max-line-length + require(_isContract(address(token)), "SafeERC20: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = address(token).call(data); + require(success, "SafeERC20: low-level call failed"); + + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } + + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `_isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function _isContract(address account) private view returns (bool) { + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != accountHash && codehash != 0x0); + } +} diff --git a/smart-contracts/contracts/zilbridge/2/LockProxyTokenManagerUpgradeableV3.sol b/smart-contracts/contracts/zilbridge/2/LockProxyTokenManagerUpgradeableV3.sol new file mode 100644 index 0000000..b07e106 --- /dev/null +++ b/smart-contracts/contracts/zilbridge/2/LockProxyTokenManagerUpgradeableV3.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {TokenManagerUpgradeableV3, ITokenManager} from "contracts/periphery/TokenManagerV3/TokenManagerUpgradeableV3.sol"; +import {BridgedToken} from "contracts/periphery/BridgedToken.sol"; +import { LockProxy } from "contracts/zilbridge/1/lockProxy.sol"; +import {IERC20} from "contracts/periphery/LockAndReleaseTokenManagerUpgradeable.sol"; + +interface ILockProxyTokenManager { + // Args in this order to match other token managers. + event SentToLockProxy(address indexed token, address indexed sender, uint amount); + event WithdrawnFromLockProxy(address indexed token, address indexed receipient, uint amount); +} + +contract LockProxyTokenManagerUpgradeableV3 is TokenManagerUpgradeableV3, ILockProxyTokenManager { + address _lockProxyAddress; + address public constant NATIVE_ASSET_HASH = address(0); + + constructor(address lockProxyAddress) { + _disableInitializers(); + _lockProxyAddress = lockProxyAddress; + } + + function changeLockProxy(address newLockProxy) public onlyOwner { + _lockProxyAddress = newLockProxy; + } + + // Incoming currency - transfer into the lock proxy + function _handleTransfer(address token, address from, uint amount) internal override { + // Just transfer value to the lock proxy. + if (token == NATIVE_ASSET_HASH) { + (bool success, ) = _lockProxyAddress.call{value: amount}(""); + emit SentToLockProxy(token, from, amount); + require(success, "Transfer failed"); + return; + } + + IERC20 erc20token = IERC20(token); + erc20token.transferFrom(from, address(_lockProxyAddress), amount); + emit SentToLockProxy(token, from, amount); + } + + function _handleAccept(address token, address recipient, uint amount) internal override { + LockProxy lp = LockProxy(payable(_lockProxyAddress)); + // Sadly, extensionTransfer() takes the same arguments as the withdrawn event but in a + // different order. This will automagically transfer native token if token==0. + lp.extensionTransfer(recipient, token, amount); + emit WithdrawnFromLockProxy(token, recipient, amount); + } +} diff --git a/smart-contracts/contracts/zilbridge/2/ccmExtendCrossChainManager.sol b/smart-contracts/contracts/zilbridge/2/ccmExtendCrossChainManager.sol new file mode 100644 index 0000000..94920f7 --- /dev/null +++ b/smart-contracts/contracts/zilbridge/2/ccmExtendCrossChainManager.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.20; + +import { Utils, ZeroCopySink, IEthCrossChainManager, IUpgradableECCM, + UpgradableECCM, IEthCrossChainData, EthCrossChainManager } from "contracts/zilbridge/1/ccmCrossChainManager.sol"; +import { Ownable } from "lib/openzeppelin-contracts/contracts/access/Ownable.sol"; + + + +interface ILockProxy { + function addExtension( + bytes calldata _argsBz, + bytes calldata /* _fromContractAddr */, + uint64 _fromChainId + ) external returns (bool); + + function removeExtension( + bytes calldata _argsBz, + bytes calldata /* _fromContractAddr */, + uint64 _fromChainId + ) external returns (bool); +} + +// This is a contract which can replace the CCM. It allows the owner to register lock proxy extensions, +// and forwards all other requests to the original cross chain manager. +// see docs/zilbridge.md for details. +// We can't implement IEthCrossChainManager, because we use the fallback for that. +contract EthExtendCrossChainManager is EthCrossChainManager { + address public _extensionManager; + event ExtensionManagerTransferred(address indexed previousExtender, address indexed newExtender); + + constructor(address _eccd, + uint64 _chainId, + address[] memory fromContractWhiteList, + bytes[] memory contractMethodWhiteList) EthCrossChainManager(_eccd, _chainId, fromContractWhiteList, contractMethodWhiteList) + { + _extensionManager = payable(msg.sender); + emit ExtensionManagerTransferred(address(0), _extensionManager); + } + + function extensionManager() public view returns (address) { return _extensionManager; } + + function isExtensionManager() public view returns (bool) { + return payable(msg.sender) == _extensionManager; + } + + modifier onlyExtensionManager() { + require(isExtensionManager(), "CCM: Caller is not the extension manager"); + _; + } + + function transferExtensionManagement(address newManager) public onlyExtensionManager { + _transferExtensionManagement(newManager); + } + + function renounceExtensionManagement() public onlyExtensionManager { + emit ExtensionManagerTransferred(_extensionManager, address(0)); + _extensionManager = address(0); + } + + function _transferExtensionManagement(address newManager) internal { + require(newManager != address(0), "ExtensionManager: new owner is 0 address"); + emit ExtensionManagerTransferred(_extensionManager, newManager); + _extensionManager = newManager; + } + + function forciblyAddExtension(address targetAddress, address addressToRegister, uint64 fromChainId) external onlyExtensionManager { + ILockProxy lockProxy = ILockProxy(targetAddress); + bytes memory payload = ZeroCopySink.WriteVarBytes(Utils.addressToBytes(addressToRegister)); + lockProxy.addExtension(payload, payload, fromChainId); + } +} diff --git a/smart-contracts/test/zilbridge/DeployZilBridge.t.sol b/smart-contracts/test/zilbridge/DeployZilBridge.t.sol new file mode 100644 index 0000000..588d545 --- /dev/null +++ b/smart-contracts/test/zilbridge/DeployZilBridge.t.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.20; + +import "forge-std/console.sol"; +import {Tester} from "test/Tester.sol"; +import {TestToken} from "test/Helpers.sol"; +import { LockProxy } from "contracts/zilbridge/1/lockProxy.sol"; +import { EthCrossChainManagerProxy } from "contracts/zilbridge/1/ccmProxy.sol"; +import { EthCrossChainManager } from "contracts/zilbridge/1/ccmCrossChainManager.sol"; +import { EthCrossChainData } from "contracts/zilbridge/1/ethCrossChainData.sol"; +import { EthExtendCrossChainManager } from "contracts/zilbridge/2/ccmExtendCrossChainManager.sol"; +import { LockProxyTokenManagerUpgradeableV3 } from "contracts/zilbridge/2/LockProxyTokenManagerUpgradeableV3.sol"; + + +abstract contract ZilBridgeFixture is Tester { + address owner = vm.createWallet("owner").addr; + address tokenDeployer = vm.createWallet("tokenDeployer").addr; + address other = vm.createWallet("other").addr; + address third = vm.createWallet("third").addr; + uint64 constant COUNTERPART_CHAIN_ID = 5; + uint64 constant CHAIN_ID = 2; + + TestToken testToken; + EthCrossChainManager ccm; + EthCrossChainManagerProxy ccmProxy; + EthCrossChainData eccd; + LockProxy lockProxy; + EthExtendCrossChainManager extendCCM; + LockProxyTokenManagerUpgradeableV3 lpTokenManager; + + function setUp() internal { + vm.prank(tokenDeployer); + testToken = new TestToken(10_000); + } + + function deployOriginalContracts() internal { + vm.startPrank(owner); + address[] memory a = new address[](0); + bytes[] memory b = new bytes[](0); + console.log("deploy_as = %s", owner); + eccd = new EthCrossChainData(); + ccm = new EthCrossChainManager(address(eccd), CHAIN_ID, a,b); + ccmProxy = new EthCrossChainManagerProxy(address(ccm)); + // Now give the ccm to the ccmProxy + ccm.transferOwnership(address(ccmProxy)); + // and give the data to the ccm. + eccd.transferOwnership(address(ccm)); + lockProxy = new LockProxy(address(ccmProxy), COUNTERPART_CHAIN_ID); + vm.stopPrank(); + } + + function installExtendCrossChainManager(address act_as) internal { + vm.startPrank(act_as); + console.log("act_as = %s", act_as); + address[] memory a = new address[](0); + bytes[] memory b = new bytes[](0); + extendCCM = new EthExtendCrossChainManager(address(eccd), 2, a, b); + console.log("ccmProxy owner = %s", ccmProxy.owner()); + console.log("ccmProxy ccm = %s", ccmProxy.getEthCrossChainManager()); + console.log("eccm owner = %s", ccm.owner()); + console.log("eccd owner = %s", eccd.owner()); + console.log("ccm = %s", address(ccm)); + ccmProxy.pauseEthCrossChainManager(); + extendCCM.transferOwnership(address(ccmProxy)); + require(ccmProxy.upgradeEthCrossChainManager(address(extendCCM))); + ccmProxy.unpauseEthCrossChainManager(); + vm.stopPrank(); + } + + function setUpZilBridgeForTesting() internal { + deployOriginalContracts(); + installExtendCrossChainManager(owner); + } + + function installTokenManager() internal { + vm.startPrank(owner); + // Create a lock proxy token manager. + lpTokenManager = new LockProxyTokenManagerUpgradeableV3(address(lockProxy)); + // Make it an extension + extendCCM.forciblyAddExtension(address(lockProxy), address(lpTokenManager), COUNTERPART_CHAIN_ID); + vm.stopPrank(); + } +} + +contract DeployZilBridgeTest is ZilBridgeFixture { + function test_ZilBridgeDeploy() external { + setUp(); + deployOriginalContracts(); + // The owner of the proxy is us + require(ccmProxy.owner() == owner); + // The ccmProxy's ccm is the ccm we installed. + require(ccmProxy.getEthCrossChainManager() == address(ccm)); + // The owner of the ccm is the ccmProxy + require(ccm.owner() == address(ccmProxy)); + // the eccd is installed + require(ccm.EthCrossChainDataAddress() == address(eccd)); + // the eccd's owner is the ccm + require(eccd.owner() == address(ccm)); + } + + function test_ZilBridgeUpgrade() external { + setUp(); + deployOriginalContracts(); + installExtendCrossChainManager(owner); + + // The owner of the proxy is us + require(ccmProxy.owner() == owner); + // The ccmProxy's ccm is the new CCM + require(ccmProxy.getEthCrossChainManager() == address(extendCCM)); + // The ccm's owner is the ccm Proxy + require(extendCCM.owner() == address(ccmProxy)); + // The ccm's eccd is intact + require(extendCCM.EthCrossChainDataAddress() == address(eccd)); + // The eccd's owner is the ccm. + require(eccd.owner() == address(extendCCM)); + + // We're unpaused + require(!extendCCM.paused()); + require(!ccmProxy.paused()); + } +} diff --git a/smart-contracts/test/zilbridge/ZilBridgeTransfer.t.sol b/smart-contracts/test/zilbridge/ZilBridgeTransfer.t.sol new file mode 100644 index 0000000..061b658 --- /dev/null +++ b/smart-contracts/test/zilbridge/ZilBridgeTransfer.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.20; + +import "forge-std/console.sol"; +import {Tester} from "test/Tester.sol"; +import {TestToken} from "test/Helpers.sol"; +import { LockProxy } from "contracts/zilbridge/1/lockProxy.sol"; +import { EthCrossChainManagerProxy } from "contracts/zilbridge/1/ccmProxy.sol"; +import { EthCrossChainManager } from "contracts/zilbridge/1/ccmCrossChainManager.sol"; +import { EthCrossChainData } from "contracts/zilbridge/1/ethCrossChainData.sol"; +import { EthExtendCrossChainManager } from "contracts/zilbridge/2/ccmExtendCrossChainManager.sol"; +import { ZilBridgeFixture } from "./DeployZilBridge.t.sol"; + +contract ZilBridgeTransfer is ZilBridgeFixture { + function test_installNativeTokenBridge() internal { + setUpZilBridgeForTesting(); + installTokenManager(); + } +} diff --git a/smart-contracts/test/zilbridge/ccmExtendCrossChainManager.t.sol b/smart-contracts/test/zilbridge/ccmExtendCrossChainManager.t.sol new file mode 100644 index 0000000..0e9c750 --- /dev/null +++ b/smart-contracts/test/zilbridge/ccmExtendCrossChainManager.t.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.20; + +import "forge-std/console.sol"; +import {Tester} from "test/Tester.sol"; +import {TestToken} from "test/Helpers.sol"; +import { EthExtendCrossChainManager } from "contracts/zilbridge/2/ccmExtendCrossChainManager.sol"; +import { Utils, ZeroCopySink, ZeroCopySource } from "contracts/zilbridge/1/ccmCrossChainManager.sol"; +import { EthCrossChainData } from "contracts/zilbridge/1/ethCrossChainData.sol"; +import { ZilBridgeFixture } from "./DeployZilBridge.t.sol"; + +contract ccmExtendCrossChainManagerFixture is ZilBridgeFixture { + + function test_extensionManagerValue() external { + setUpZilBridgeForTesting(); + require(extendCCM._extensionManager() == owner); + require(extendCCM.extensionManager() == owner); + } + + function testFail_invalidRenounce() external { + setUpZilBridgeForTesting(); + vm.startPrank(other); + extendCCM.renounceExtensionManagement(); + vm.stopPrank(); + } + + function testFail_invalidTransfer() external { + setUpZilBridgeForTesting(); + vm.startPrank(other); + extendCCM.transferExtensionManagement(third); + vm.stopPrank(); + } + + function test_transferExtensionManager() external { + setUpZilBridgeForTesting(); + vm.startPrank(owner); + extendCCM.transferExtensionManagement(other); + vm.stopPrank(); + vm.startPrank(other); + extendCCM.transferExtensionManagement(third); + vm.stopPrank(); + } + + // TODO: remainder of the authorisation tests for transferExtensionManager. + + function test_addExtension() external { + setUpZilBridgeForTesting(); + installTokenManager(); + // Check that the token manager is present. + require(lockProxy.extensions(address(lpTokenManager))==true); + } + + function test_Pickle() external { + console.log("owner = %s", owner); + bytes memory payload = ZeroCopySink.WriteVarBytes(Utils.addressToBytes(owner)); + console.logBytes(payload); + uint256 off = 0; + bytes memory result; + { + bytes1 v; + uint256 offset = 0; + (v,offset) = ZeroCopySource.NextByte(payload, offset); + console.log("F"); + console.logBytes1(v); + } + { + uint len; + uint256 offset = 0; + (len,offset) = ZeroCopySource.NextVarUint(payload, offset); + console.log("len = %d offset = %d payload = %d", len, offset, payload.length); + } + (result, off) = ZeroCopySource.NextVarBytes(payload, off); + console.logBytes(result); + } +} +