Quellcode durchsuchen

ethereum generalized messaging & governance

Change-Id: I412111a10749050ca095458f2fcfe81ce0fe189c
valentin vor 4 Jahren
Ursprung
Commit
c3fa835196

+ 48 - 0
ethereum/contracts/Getters.sol

@@ -0,0 +1,48 @@
+// contracts/Getters.sol
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+import "./State.sol";
+
+contract Getters is State {
+    function getGuardianSet(uint32 index) public view returns (Structs.GuardianSet memory) {
+        return _state.guardianSets[index];
+    }
+
+    function getCurrentGuardianSetIndex() public view returns (uint32) {
+        return _state.guardianSetIndex;
+    }
+
+    function getGuardianSetExpiry() public view returns (uint32) {
+        return _state.guardianSetExpiry;
+    }
+
+    function governanceActionIsConsumed(bytes32 hash) public view returns (bool) {
+        return _state.consumedGovernanceActions[hash];
+    }
+
+    function isInitialized(address impl) public view returns (bool) {
+        return _state.initializedImplementations[impl];
+    }
+
+    function chainId() public view returns (uint16) {
+        return _state.provider.chainId;
+    }
+
+    function governanceChainId() public view returns (uint16){
+        return _state.provider.governanceChainId;
+    }
+
+    function governanceContract() public view returns (bytes32){
+        return _state.provider.governanceContract;
+    }
+
+    function messageFee() public view returns (uint256) {
+        return _state.messageFee;
+    }
+
+    function persistedMessageFee() public view returns (uint256) {
+        return _state.persistedMessageFee;
+    }
+}

+ 132 - 0
ethereum/contracts/Governance.sol

@@ -0,0 +1,132 @@
+// contracts/Governance.sol
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+import "./Structs.sol";
+import "./GovernanceStructs.sol";
+import "./Messages.sol";
+import "./Setters.sol";
+
+import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
+
+abstract contract Governance is GovernanceStructs, Messages, Setters, ERC1967Upgrade {
+    event ContractUpgraded(address indexed oldContract, address indexed newContract);
+    event GuardianSetAdded(uint32 indexed index);
+
+    // "Core" (left padded)
+    bytes32 constant module = 0x00000000000000000000000000000000000000000000000000000000436f7265;
+
+    function submitContractUpgrade(bytes memory _vm) public {
+        Structs.VM memory vm = parseVM(_vm);
+
+        (bool isValid, string memory reason) = verifyGovernanceVM(vm);
+        require(isValid, reason);
+
+        GovernanceStructs.ContractUpgrade memory upgrade = parseContractUpgrade(vm.payload);
+
+        require(upgrade.module == module, "Invalid Module");
+        require(upgrade.chain == chainId(), "Invalid Chain");
+
+        setGovernanceActionConsumed(vm.hash);
+
+        upgradeImplementation(upgrade.newContract);
+    }
+
+    function submitSetMessageFee(bytes memory _vm) public {
+        Structs.VM memory vm = parseVM(_vm);
+
+        (bool isValid, string memory reason) = verifyGovernanceVM(vm);
+        require(isValid, reason);
+
+        GovernanceStructs.SetMessageFee memory upgrade = parseSetMessageFee(vm.payload);
+
+        require(upgrade.module == module, "Invalid Module");
+        require(upgrade.chain == chainId(), "Invalid Chain");
+
+        setGovernanceActionConsumed(vm.hash);
+
+        setMessageFee(upgrade.messageFee);
+        setPersistedMessageFee(upgrade.persistedMessageFee);
+    }
+
+    function submitNewGuardianSet(bytes memory _vm) public {
+        Structs.VM memory vm = parseVM(_vm);
+
+        (bool isValid, string memory reason) = verifyGovernanceVM(vm);
+        require(isValid, reason);
+
+        GovernanceStructs.GuardianSetUpgrade memory upgrade = parseGuardianSetUpgrade(vm.payload);
+
+        require(upgrade.module == module, "invalid Module");
+        require(upgrade.chain == chainId() || upgrade.chain == 0, "invalid Chain");
+
+        require(upgrade.newGuardianSet.keys.length > 0, "new guardian set is empty");
+        require(upgrade.newGuardianSetIndex == getCurrentGuardianSetIndex() + 1, "index must increase in steps of 1");
+
+        setGovernanceActionConsumed(vm.hash);
+
+        expireGuardianSet(getCurrentGuardianSetIndex());
+        storeGuardianSet(upgrade.newGuardianSet, upgrade.newGuardianSetIndex);
+        updateGuardianSetIndex(upgrade.newGuardianSetIndex);
+    }
+
+    function submitTransferFees(bytes memory _vm) public {
+        Structs.VM memory vm = parseVM(_vm);
+
+        (bool isValid, string memory reason) = verifyGovernanceVM(vm);
+        require(isValid, reason);
+
+        GovernanceStructs.TransferFees memory transfer = parseTransferFees(vm.payload);
+
+        require(transfer.module == module, "invalid Module");
+        require(transfer.chain == chainId() || transfer.chain == 0, "invalid Chain");
+
+        setGovernanceActionConsumed(vm.hash);
+
+        address payable recipient = payable(address(uint160(uint256(transfer.recipient))));
+
+        recipient.transfer(transfer.amount);
+    }
+
+    function upgradeImplementation(address newImplementation) internal {
+        address currentImplementation = _getImplementation();
+
+        _upgradeTo(newImplementation);
+
+        // Call initialize function of the new implementation
+        (bool success, bytes memory reason) = newImplementation.delegatecall(abi.encodeWithSignature("initialize()"));
+
+        require(success, string(reason));
+
+        emit ContractUpgraded(currentImplementation, newImplementation);
+    }
+
+    function verifyGovernanceVM(Structs.VM memory vm) internal view returns (bool, string memory){
+        // validate vm
+        (bool isValid, string memory reason) = verifyVM(vm);
+        if (!isValid){
+            return (false, reason);
+        }
+
+        // only current guardianset can sign governance packets
+        if (vm.guardianSetIndex != getCurrentGuardianSetIndex()) {
+            return (false, "not signed by current guardian set");
+        }
+
+        // verify source
+        if (uint16(vm.emitterChainId) != governanceChainId()) {
+            return (false, "wrong governance chain");
+        }
+        if (vm.emitterAddress != governanceContract()) {
+            return (false, "wrong governance contract");
+        }
+
+        // prevent re-entry
+        if (governanceActionIsConsumed(vm.hash)){
+            return (false, "governance action already consumed");
+        }
+
+        return (true, "");
+    }
+}

+ 150 - 0
ethereum/contracts/GovernanceStructs.sol

@@ -0,0 +1,150 @@
+// contracts/GovernanceStructs.sol
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+import "./libraries/external/BytesLib.sol";
+import "./Structs.sol";
+
+contract GovernanceStructs {
+    using BytesLib for bytes;
+
+    enum GovernanceAction {
+        UpgradeContract,
+        UpgradeGuardianset
+    }
+
+    struct ContractUpgrade {
+        bytes32 module;
+        uint8 action;
+        uint16 chain;
+
+        address newContract;
+    }
+
+    struct GuardianSetUpgrade {
+        bytes32 module;
+        uint8 action;
+        uint16 chain;
+
+        Structs.GuardianSet newGuardianSet;
+        uint32 newGuardianSetIndex;
+    }
+
+    struct SetMessageFee {
+        bytes32 module;
+        uint8 action;
+        uint16 chain;
+
+        uint256 messageFee;
+        uint256 persistedMessageFee;
+    }
+
+    struct TransferFees {
+        bytes32 module;
+        uint8 action;
+        uint16 chain;
+
+        uint256 amount;
+        bytes32 recipient;
+    }
+
+    function parseContractUpgrade(bytes memory encodedUpgrade) public pure returns (ContractUpgrade memory cu) {
+        uint index = 0;
+
+        cu.module = encodedUpgrade.toBytes32(index);
+        index += 32;
+
+        cu.action = encodedUpgrade.toUint8(index);
+        index += 1;
+
+        require(cu.action == 1, "invalid ContractUpgrade");
+
+        cu.chain = encodedUpgrade.toUint16(index);
+        index += 2;
+
+        cu.newContract = address(uint160(uint256(encodedUpgrade.toBytes32(index))));
+        index += 32;
+
+        require(encodedUpgrade.length == index, "invalid ContractUpgrade");
+    }
+
+    function parseGuardianSetUpgrade(bytes memory encodedUpgrade) public pure returns (GuardianSetUpgrade memory gsu) {
+        uint index = 0;
+
+        gsu.module = encodedUpgrade.toBytes32(index);
+        index += 32;
+
+        gsu.action = encodedUpgrade.toUint8(index);
+        index += 1;
+
+        require(gsu.action == 2, "invalid GuardianSetUpgrade");
+
+        gsu.chain = encodedUpgrade.toUint16(index);
+        index += 2;
+
+        gsu.newGuardianSetIndex = encodedUpgrade.toUint32(index);
+        index += 4;
+
+        uint8 guardianLength = encodedUpgrade.toUint8(index);
+        index += 1;
+
+        gsu.newGuardianSet = Structs.GuardianSet({
+            keys : new address[](guardianLength),
+            expirationTime : 0
+        });
+
+        for(uint i = 0; i < guardianLength; i++) {
+            gsu.newGuardianSet.keys[i] = encodedUpgrade.toAddress(index);
+            index += 20;
+        }
+
+        require(encodedUpgrade.length == index, "invalid GuardianSetUpgrade");
+    }
+
+    function parseSetMessageFee(bytes memory encodedSetMessageFee) public pure returns (SetMessageFee memory smf) {
+        uint index = 0;
+
+        smf.module = encodedSetMessageFee.toBytes32(index);
+        index += 32;
+
+        smf.action = encodedSetMessageFee.toUint8(index);
+        index += 1;
+
+        require(smf.action == 3, "invalid SetMessageFee");
+
+        smf.chain = encodedSetMessageFee.toUint16(index);
+        index += 2;
+
+        smf.messageFee = encodedSetMessageFee.toUint256(index);
+        index += 32;
+
+        smf.persistedMessageFee = encodedSetMessageFee.toUint256(index);
+        index += 32;
+
+        require(encodedSetMessageFee.length == index, "invalid SetMessageFee");
+    }
+
+    function parseTransferFees(bytes memory encodedTransferFees) public pure returns (TransferFees memory tf) {
+        uint index = 0;
+
+        tf.module = encodedTransferFees.toBytes32(index);
+        index += 32;
+
+        tf.action = encodedTransferFees.toUint8(index);
+        index += 1;
+
+        require(tf.action == 4, "invalid TransferFees");
+
+        tf.chain = encodedTransferFees.toUint16(index);
+        index += 2;
+
+        tf.amount = encodedTransferFees.toUint256(index);
+        index += 32;
+
+        tf.recipient = encodedTransferFees.toBytes32(index);
+        index += 32;
+
+        require(encodedTransferFees.length == index, "invalid TransferFees");
+    }
+}

+ 64 - 0
ethereum/contracts/Implementation.sol

@@ -0,0 +1,64 @@
+// contracts/Implementation.sol
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "./Governance.sol";
+
+import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
+
+contract Implementation is Governance {
+    event LogMessagePublished(address indexed sender, uint32 nonce, bytes payload, bool persistMessage);
+
+    // Publish a message to be attested by the Wormhole network
+    function publishMessage(
+        uint32 nonce,
+        bytes memory payload,
+        bool persistMessage
+    ) public payable {
+        // check fee
+        if( persistMessage ) {
+            require(msg.value == persistedMessageFee(), "invalid fee");
+        } else {
+            require(msg.value == messageFee(), "invalid fee");
+        }
+
+        // emit log
+        emit LogMessagePublished(msg.sender, nonce, payload, persistMessage);
+    }
+
+    function initialize(address[] memory initialGuardians, uint16 chainId, uint16 governanceChainId, bytes32 governanceContract) initializer public {
+        require(initialGuardians.length > 0, "no guardians specified");
+
+        Structs.GuardianSet memory initialGuardianSet = Structs.GuardianSet({
+            keys : initialGuardians,
+            expirationTime : 0
+        });
+
+        storeGuardianSet(initialGuardianSet, 0);
+        // initial guardian set index is 0, which is the default value of the storage slot anyways
+
+        setChainId(chainId);
+
+        setGovernanceChainId(governanceChainId);
+        setGovernanceContract(governanceContract);
+    }
+
+    modifier initializer() {
+        address implementation = ERC1967Upgrade._getImplementation();
+
+        require(
+            !isInitialized(implementation),
+            "already initialized"
+        );
+
+        setInitialized(implementation);
+
+        _;
+    }
+
+    fallback() external payable {revert("unsupported");}
+
+    receive() external payable {revert("the Wormhole contract does not accept assets");}
+}

+ 109 - 0
ethereum/contracts/Messages.sol

@@ -0,0 +1,109 @@
+// contracts/Messages.sol
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "./Getters.sol";
+import "./Structs.sol";
+import "./libraries/external/BytesLib.sol";
+
+
+contract Messages is Getters {
+    using BytesLib for bytes;
+
+    function parseAndVerifyVM(bytes calldata encodedVM) public view returns (Structs.VM memory vm, bool valid, string memory reason) {
+        vm = parseVM(encodedVM);
+        (valid, reason) = verifyVM(vm);
+    }
+
+    function verifyVM(Structs.VM memory vm) public view returns (bool valid, string memory reason) {
+        Structs.GuardianSet memory guardianSet = getGuardianSet(vm.guardianSetIndex);
+
+        if(guardianSet.keys.length == 0){
+            return (false, "invalid guardian set");
+        }
+        if(vm.guardianSetIndex != getCurrentGuardianSetIndex() && guardianSet.expirationTime < block.timestamp){
+            return (false, "guardian set has expired");
+        }
+        // We're using a fixed point number transformation with 1 decimal to deal with rounding.
+        if(((guardianSet.keys.length * 10 / 3) * 2) / 10 + 1 > vm.signatures.length){
+            return (false, "no quorum");
+        }
+
+        // Verify signatures
+        (bool signaturesValid, string memory invalidReason) = verifySignatures(vm.hash, vm.signatures, guardianSet);
+        if(!signaturesValid){
+            return (false, invalidReason);
+        }
+
+        return (true, "");
+    }
+
+    function verifySignatures(bytes32 hash, Structs.Signature[] memory signatures, Structs.GuardianSet memory guardianSet) public pure returns (bool valid, string memory reason) {
+        uint8 lastIndex = 0;
+        for (uint i = 0; i < signatures.length; i++) {
+            Structs.Signature memory sig = signatures[i];
+
+            require(i == 0 || sig.guardianIndex > lastIndex, "signature indices must be ascending");
+            lastIndex = sig.guardianIndex;
+
+            if(ecrecover(hash, sig.v, sig.r, sig.s) != guardianSet.keys[sig.guardianIndex]){
+                return (false, "VM signature invalid");
+            }
+        }
+        return (true, "");
+    }
+
+    function parseVM(bytes memory encodedVM) public pure virtual returns (Structs.VM memory vm) {
+        uint index = 0;
+
+        vm.version = encodedVM.toUint8(index);
+        index += 1;
+        require(vm.version == 1, "VM version incompatible");
+
+        vm.guardianSetIndex = encodedVM.toUint32(index);
+        index += 4;
+
+        // Parse Signatures
+        uint256 signersLen = encodedVM.toUint8(index);
+        index += 1;
+        vm.signatures = new Structs.Signature[](signersLen);
+        for (uint i = 0; i < signersLen; i++) {
+            vm.signatures[i].r = encodedVM.toBytes32(index);
+            index += 32;
+            vm.signatures[i].s = encodedVM.toBytes32(index);
+            index += 32;
+            vm.signatures[i].v = encodedVM.toUint8(index) + 27;
+            index += 1;
+
+            vm.signatures[i].guardianIndex = encodedVM.toUint8(index);
+            index += 1;
+        }
+
+        // Hash the body
+        bytes memory body = encodedVM.slice(index, encodedVM.length - index);
+        vm.hash = keccak256(body);
+
+        // Parse the body
+        vm.timestamp = encodedVM.toUint32(index);
+        index += 4;
+
+        vm.nonce = encodedVM.toUint32(index);
+        index += 4;
+
+        vm.emitterChainId = encodedVM.toUint16(index);
+        index += 2;
+
+        vm.emitterAddress = encodedVM.toBytes32(index);
+        index += 32;
+
+        uint8 payloadLen = encodedVM.toUint8(index);
+        index += 1;
+
+        vm.payload = encodedVM.slice(index, payloadLen);
+        index += payloadLen;
+
+        require(encodedVM.length == index, "invalid VM");
+    }
+}

+ 19 - 0
ethereum/contracts/Migrations.sol

@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.4.22 <0.9.0;
+
+contract Migrations {
+    address public owner = msg.sender;
+    uint public last_completed_migration;
+
+    modifier restricted() {
+        require(
+            msg.sender == owner,
+            "This function is restricted to the contract's owner"
+        );
+        _;
+    }
+
+    function setCompleted(uint completed) public restricted {
+        last_completed_migration = completed;
+    }
+}

+ 0 - 211
ethereum/contracts/Module-ERC20.sol

@@ -1,211 +0,0 @@
-// contracts/Module-ERC20.sol
-// SPDX-License-Identifier: Apache 2
-
-pragma solidity ^0.6.0;
-pragma experimental ABIEncoderV2;
-
-import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
-import "@openzeppelin/contracts/math/SafeMath.sol";
-import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
-import "./BytesLib.sol";
-import "./WrappedAsset.sol";
-import "./Wormhole.sol";
-
-contract ERC20Bridge is ReentrancyGuard {
-    using SafeERC20 for IERC20;
-    using BytesLib for bytes;
-    using SafeMath for uint256;
-
-    uint8 public CHAIN_ID = 2;
-    uint64 constant MAX_UINT64 = 18_446_744_073_709_551_615;
-
-    // Address of the Wrapped asset template
-    address public wrappedAssetMaster;
-    // Address of the Wormhole
-    Wormhole public wormhole;
-
-    // Address of the official WETH contract
-    address constant WETHAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
-
-    event LogTokensLocked(
-        uint8 target_chain,
-        uint8 token_chain,
-        uint8 token_decimals,
-        bytes32 indexed token,
-        bytes32 indexed sender,
-        bytes32 recipient,
-        uint256 amount,
-        uint32 nonce
-    );
-
-    // Mapping of already consumedVAAs
-    mapping(bytes32 => bool) public consumedVAAs;
-
-    // Mapping of wrapped asset ERC20 contracts
-    mapping(bytes32 => address) public wrappedAssets;
-    mapping(address => bool) public isWrappedAsset;
-
-    constructor(address wrapped_asset_master, address payable wormhole_bridge) public {
-        wrappedAssetMaster = wrapped_asset_master;
-        wormhole = Wormhole(wormhole_bridge);
-    }
-
-    function submitVAA(
-        bytes calldata vaa
-    ) public nonReentrant {
-        Wormhole.ParsedVAA memory parsed_vaa = wormhole.parseAndVerifyVAA(vaa);
-        require(!consumedVAAs[parsed_vaa.hash], "vaa was already executed");
-
-        // Set the VAA as consumed
-        consumedVAAs[parsed_vaa.hash] = true;
-
-        // Execute transfer
-        vaaTransfer(parsed_vaa.payload);
-    }
-
-    function vaaTransfer(bytes memory data) private {
-        //uint32 nonce = data.toUint64(0);
-        uint8 source_chain = data.toUint8(4);
-
-        uint8 target_chain = data.toUint8(5);
-        //bytes32 source_address = data.toBytes32(6);
-        //bytes32 target_address = data.toBytes32(38);
-        address target_address = data.toAddress(38 + 12);
-
-        uint8 token_chain = data.toUint8(70);
-        //bytes32 token_address = data.toBytes32(71);
-        uint256 amount = data.toUint256(104);
-
-        require(source_chain != target_chain, "same chain transfers are not supported");
-        require(target_chain == CHAIN_ID, "transfer must be incoming");
-
-        if (token_chain != CHAIN_ID) {
-            bytes32 token_address = data.toBytes32(71);
-            bytes32 asset_id = keccak256(abi.encodePacked(token_chain, token_address));
-
-            // if yes: mint to address
-            // if no: create and mint
-            address wrapped_asset = wrappedAssets[asset_id];
-            if (wrapped_asset == address(0)) {
-                uint8 asset_decimals = data.toUint8(103);
-                wrapped_asset = deployWrappedAsset(asset_id, token_chain, token_address, asset_decimals);
-            }
-
-            WrappedAsset(wrapped_asset).mint(target_address, amount);
-        } else {
-            address token_address = data.toAddress(71 + 12);
-
-            uint8 decimals = ERC20(token_address).decimals();
-
-            // Readjust decimals if they've previously been truncated
-            if (decimals > 9) {
-                amount = amount.mul(10 ** uint256(decimals - 9));
-            }
-            IERC20(token_address).safeTransfer(target_address, amount);
-        }
-    }
-
-    function deployWrappedAsset(bytes32 seed, uint8 token_chain, bytes32 token_address, uint8 decimals) private returns (address asset){
-        // Taken from https://github.com/OpenZeppelin/openzeppelin-sdk/blob/master/packages/lib/contracts/upgradeability/ProxyFactory.sol
-        // Licensed under MIT
-        bytes20 targetBytes = bytes20(wrappedAssetMaster);
-        assembly {
-            let clone := mload(0x40)
-            mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
-            mstore(add(clone, 0x14), targetBytes)
-            mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
-            asset := create2(0, clone, 0x37, seed)
-        }
-
-        // Call initializer
-        WrappedAsset(asset).initialize(token_chain, token_address, decimals);
-
-        // Store address
-        wrappedAssets[seed] = asset;
-        isWrappedAsset[asset] = true;
-    }
-
-    function lockAssets(
-        address asset,
-        uint256 amount,
-        bytes32 recipient,
-        uint8 target_chain,
-        uint32 nonce,
-        bool refund_dust
-    ) public nonReentrant {
-        require(target_chain != CHAIN_ID, "must not transfer to the same chain");
-
-        uint8 asset_chain = CHAIN_ID;
-        bytes32 asset_address;
-        uint8 decimals = ERC20(asset).decimals();
-
-        if (isWrappedAsset[asset]) {
-            WrappedAsset(asset).burn(msg.sender, amount);
-            asset_chain = WrappedAsset(asset).assetChain();
-            asset_address = WrappedAsset(asset).assetAddress();
-        } else {
-            uint256 balanceBefore = IERC20(asset).balanceOf(address(this));
-            IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
-            uint256 balanceAfter = IERC20(asset).balanceOf(address(this));
-
-            // The amount that was transferred in is the delta between balance before and after the transfer.
-            // This is to properly handle tokens that charge a fee on transfer.
-            amount = balanceAfter.sub(balanceBefore);
-
-            // Decimal adjust amount - we keep the dust
-            if (decimals > 9) {
-                uint256 original_amount = amount;
-                amount = amount.div(10 ** uint256(decimals - 9));
-
-                if (refund_dust) {
-                    IERC20(asset).safeTransfer(msg.sender, original_amount.mod(10 ** uint256(decimals - 9)));
-                }
-
-                decimals = 9;
-            }
-
-            require(balanceAfter.div(10 ** uint256(ERC20(asset).decimals() - 9)) <= MAX_UINT64, "bridge balance would exceed maximum");
-
-            asset_address = bytes32(uint256(asset));
-        }
-
-        // Check here after truncation
-        require(amount != 0, "truncated amount must not be 0");
-
-        emit LogTokensLocked(target_chain, asset_chain, decimals, asset_address, bytes32(uint256(msg.sender)), recipient, amount, nonce);
-    }
-
-    function lockETH(
-        bytes32 recipient,
-        uint8 target_chain,
-        uint32 nonce
-    ) public payable nonReentrant {
-        require(target_chain != CHAIN_ID, "must not transfer to the same chain");
-
-        uint256 remainder = msg.value.mod(10 ** 9);
-        uint256 transfer_amount = msg.value.div(10 ** 9);
-        require(transfer_amount != 0, "truncated amount must not be 0");
-
-        // Transfer back remainder
-        msg.sender.transfer(remainder);
-
-        // Wrap tx value in WETH
-        WETH(WETHAddress).deposit{value : msg.value - remainder}();
-
-        // Log deposit of WETH
-        emit LogTokensLocked(target_chain, CHAIN_ID, 9, bytes32(uint256(WETHAddress)), bytes32(uint256(msg.sender)), recipient, transfer_amount, nonce);
-    }
-
-    fallback() external payable {revert("please use lockETH to transfer ETH to Solana");}
-
-    receive() external payable {revert("please use lockETH to transfer ETH to Solana");}
-}
-
-
-interface WETH is IERC20 {
-    function deposit() external payable;
-
-    function withdraw(uint256 amount) external;
-}

+ 48 - 0
ethereum/contracts/Setters.sol

@@ -0,0 +1,48 @@
+// contracts/Setters.sol
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+import "./State.sol";
+
+contract Setters is State {
+    function updateGuardianSetIndex(uint32 newIndex) internal {
+        _state.guardianSetIndex = newIndex;
+    }
+
+    function expireGuardianSet(uint32 index) internal {
+        _state.guardianSets[index].expirationTime = uint32(block.timestamp) + 86400;
+    }
+
+    function storeGuardianSet(Structs.GuardianSet memory set, uint32 index) internal {
+        _state.guardianSets[index] = set;
+    }
+
+    function setInitialized(address implementatiom) internal {
+        _state.initializedImplementations[implementatiom] = true;
+    }
+
+    function setGovernanceActionConsumed(bytes32 hash) internal {
+        _state.consumedGovernanceActions[hash] = true;
+    }
+
+    function setChainId(uint16 chainId) internal {
+        _state.provider.chainId = chainId;
+    }
+
+    function setGovernanceChainId(uint16 chainId) internal {
+        _state.provider.governanceChainId = chainId;
+    }
+
+    function setGovernanceContract(bytes32 governanceContract) internal {
+        _state.provider.governanceContract = governanceContract;
+    }
+
+    function setMessageFee(uint256 newFee) internal {
+        _state.messageFee = newFee;
+    }
+
+    function setPersistedMessageFee(uint256 newFee) internal {
+        _state.persistedMessageFee = newFee;
+    }
+}

+ 47 - 0
ethereum/contracts/State.sol

@@ -0,0 +1,47 @@
+// contracts/State.sol
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+import "./Structs.sol";
+
+contract Events {
+    event LogGuardianSetChanged(
+        uint32 oldGuardianIndex,
+        uint32 newGuardianIndex
+    );
+
+    event LogMessagePublished(
+        address emitter_address,
+        uint32 nonce,
+        bytes payload
+    );
+}
+
+contract Storage {
+    struct WormholeState {
+        Structs.Provider provider;
+
+        // Mapping of guardian_set_index => guardian set
+        mapping(uint32 => Structs.GuardianSet) guardianSets;
+
+        // Current active guardian set index
+        uint32 guardianSetIndex;
+
+        // Period for which a guardian set stays active after it has been replaced
+        uint32 guardianSetExpiry;
+
+        // Mapping of consumed governance actions
+        mapping(bytes32 => bool) consumedGovernanceActions;
+
+        // Mapping of initialized implementations
+        mapping(address => bool) initializedImplementations;
+
+        uint256 messageFee;
+        uint256 persistedMessageFee;
+    }
+}
+
+contract State {
+    Storage.WormholeState _state;
+}

+ 38 - 0
ethereum/contracts/Structs.sol

@@ -0,0 +1,38 @@
+// contracts/Structs.sol
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+interface Structs {
+	struct Provider {
+		uint16 chainId;
+		uint16 governanceChainId;
+		bytes32 governanceContract;
+	}
+
+	struct GuardianSet {
+		address[] keys;
+		uint32 expirationTime;
+	}
+
+	struct Signature {
+		bytes32 r;
+		bytes32 s;
+		uint8 v;
+		uint8 guardianIndex;
+	}
+
+	struct VM {
+		uint8 version;
+		uint32 timestamp;
+		uint32 nonce;
+		uint16 emitterChainId;
+		bytes32 emitterAddress;
+		bytes payload;
+
+		uint32 guardianSetIndex;
+		Signature[] signatures;
+
+		bytes32 hash;
+	}
+}

+ 8 - 149
ethereum/contracts/Wormhole.sol

@@ -1,154 +1,13 @@
 // contracts/Wormhole.sol
 // contracts/Wormhole.sol
 // SPDX-License-Identifier: Apache 2
 // SPDX-License-Identifier: Apache 2
 
 
-pragma solidity ^0.6.0;
-pragma experimental ABIEncoderV2;
+pragma solidity ^0.8.0;
 
 
-import "@openzeppelin/contracts/math/SafeMath.sol";
-import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
-import "./BytesLib.sol";
+import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
 
 
-contract Wormhole is ReentrancyGuard {
-    using BytesLib for bytes;
-
-    // Chain ID of Ethereum
-    uint8 public CHAIN_ID = 2;
-
-    struct GuardianSet {
-        address[] keys;
-        uint32 expiration_time;
-    }
-
-    event LogGuardianSetChanged(
-        uint32 oldGuardianIndex,
-        uint32 newGuardianIndex
-    );
-
-    event LogMessagePublished(
-        address emitter_address,
-        uint32 nonce,
-        bytes payload
-    );
-
-    struct ParsedVAA {
-        uint8 version;
-        bytes32 hash;
-        uint32 guardian_set_index;
-        uint32 timestamp;
-        uint8 action;
-        bytes payload;
-    }
-
-    // Mapping of guardian_set_index => guardian set
-    mapping(uint32 => GuardianSet) public guardian_sets;
-    // Current active guardian set
-    uint32 public guardian_set_index;
-
-    // Period for which a guardian set stays active after it has been replaced
-    uint32 public guardian_set_expirity;
-
-    // Mapping of already consumedVAAs
-    mapping(bytes32 => bool) public consumedVAAs;
-
-    constructor(GuardianSet memory initial_guardian_set, uint32 _guardian_set_expirity) public {
-        guardian_sets[0] = initial_guardian_set;
-        // Explicitly set for doc purposes
-        guardian_set_index = 0;
-        guardian_set_expirity = _guardian_set_expirity;
-    }
-
-    function getGuardianSet(uint32 idx) view public returns (GuardianSet memory gs) {
-        return guardian_sets[idx];
-    }
-
-    // Publish a message to be attested by the Wormhole network
-    function publishMessage(
-        uint32 nonce,
-        bytes memory payload
-    ) public {
-        emit LogMessagePublished(msg.sender, nonce, payload);
-    }
-
-    // Enact a governance VAA
-    function executeGovernanceVAA(
-        bytes calldata vaa
-    ) public nonReentrant {
-        ParsedVAA memory parsed_vaa = parseAndVerifyVAA(vaa);
-        // Process VAA
-        if (parsed_vaa.action == 0x01) {
-            require(parsed_vaa.guardian_set_index == guardian_set_index, "only the current guardian set can change the guardian set");
-            vaaUpdateGuardianSet(parsed_vaa.payload);
-        } else {
-            revert("invalid VAA action");
-        }
-
-        // Set the VAA as consumed
-        consumedVAAs[parsed_vaa.hash] = true;
-    }
-
-    // parseAndVerifyVAA parses raw VAA data into a struct and verifies whether it contains sufficient signatures of an
-    // active guardian set i.e. is valid according to Wormhole consensus rules.
-    function parseAndVerifyVAA(bytes calldata vaa) public view returns (ParsedVAA memory parsed_vaa) {
-        parsed_vaa.version = vaa.toUint8(0);
-        require(parsed_vaa.version == 1, "VAA version incompatible");
-
-        // Load 4 bytes starting from index 1
-        parsed_vaa.guardian_set_index = vaa.toUint32(1);
-
-        uint256 len_signers = vaa.toUint8(5);
-        uint offset = 6 + 66 * len_signers;
-
-        // Load 4 bytes timestamp
-        parsed_vaa.timestamp = vaa.toUint32(offset);
-
-        // Hash the body
-        parsed_vaa.hash = keccak256(vaa.slice(offset, vaa.length - offset));
-        require(!consumedVAAs[parsed_vaa.hash], "VAA was already executed");
-
-        GuardianSet memory guardian_set = guardian_sets[parsed_vaa.guardian_set_index];
-        require(guardian_set.keys.length > 0, "invalid guardian set");
-        require(guardian_set.expiration_time == 0 || guardian_set.expiration_time > block.timestamp, "guardian set has expired");
-        // We're using a fixed point number transformation with 1 decimal to deal with rounding.
-        require(((guardian_set.keys.length * 10 / 3) * 2) / 10 + 1 <= len_signers, "no quorum");
-
-        int16 last_index = - 1;
-        for (uint i = 0; i < len_signers; i++) {
-            uint8 index = vaa.toUint8(6 + i * 66);
-            require(index > last_index, "signature indices must be ascending");
-            last_index = int16(index);
-
-            bytes32 r = vaa.toBytes32(7 + i * 66);
-            bytes32 s = vaa.toBytes32(39 + i * 66);
-            uint8 v = vaa.toUint8(71 + i * 66);
-            v += 27;
-            require(ecrecover(parsed_vaa.hash, v, r, s) == guardian_set.keys[index], "VAA signature invalid");
-        }
-
-        parsed_vaa.payload = vaa.slice(offset + 4, vaa.length - (offset + 4));
-    }
-
-    function vaaUpdateGuardianSet(bytes memory data) private {
-        uint32 new_guardian_set_index = data.toUint32(0);
-        require(new_guardian_set_index == guardian_set_index + 1, "index must increase in steps of 1");
-        uint8 len = data.toUint8(4);
-
-        address[] memory new_guardians = new address[](len);
-        for (uint i = 0; i < len; i++) {
-            address addr = data.toAddress(5 + i * 20);
-            new_guardians[i] = addr;
-        }
-
-        uint32 old_guardian_set_index = guardian_set_index;
-        guardian_set_index = new_guardian_set_index;
-
-        GuardianSet memory new_guardian_set = GuardianSet(new_guardians, 0);
-        guardian_sets[guardian_set_index] = new_guardian_set;
-        guardian_sets[old_guardian_set_index].expiration_time = uint32(block.timestamp) + guardian_set_expirity;
-
-        emit LogGuardianSetChanged(old_guardian_set_index, guardian_set_index);
-    }
-
-    fallback() external payable {revert("unsupported");}
-
-    receive() external payable {revert("the Wormhole core does not accept assets");}
-}
+contract Wormhole is ERC1967Proxy {
+    constructor (address implementation, bytes memory initData) ERC1967Proxy(
+        implementation,
+        initData
+    ) { }
+}

+ 0 - 310
ethereum/contracts/WrappedAsset.sol

@@ -1,310 +0,0 @@
-// contracts/WrappedAsset.sol
-// SPDX-License-Identifier: Apache 2
-pragma solidity ^0.6.0;
-
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import "@openzeppelin/contracts/math/SafeMath.sol";
-import "@openzeppelin/contracts/utils/Address.sol";
-import "@openzeppelin/contracts/GSN/Context.sol";
-
-contract WrappedAsset is IERC20, Context {
-    uint8 public assetChain;
-    bytes32 public assetAddress;
-    bool public initialized;
-    address public bridge;
-
-    function initialize(uint8 _assetChain, bytes32 _assetAddress, uint8 decimals) public {
-        require(!initialized, "already initialized");
-        // Set local fields
-        assetChain = _assetChain;
-        assetAddress = _assetAddress;
-        bridge = msg.sender;
-        initialized = true;
-
-        _symbol = "WWT";
-        _decimals = decimals;
-    }
-
-    function mint(address account, uint256 amount) external {
-        require(msg.sender == bridge, "mint can only be called by the bridge");
-
-        _mint(account, amount);
-    }
-
-    function burn(address account, uint256 amount) external {
-        require(msg.sender == bridge, "burn can only be called by the bridge");
-
-        _burn(account, amount);
-    }
-
-    // Taken from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol
-    // Licensed under MIT
-
-    using SafeMath for uint256;
-    using Address for address;
-
-    mapping(address => uint256) private _balances;
-
-    mapping(address => mapping(address => uint256)) private _allowances;
-
-    uint256 private _totalSupply;
-
-    string private _symbol;
-    uint8 private _decimals = 18;
-
-    /**
-     * @dev Returns the name of the token.
-     */
-    function name() public view returns (string memory) {
-        return string(abi.encodePacked("Wormhole Wrapped - ", uintToString(assetChain), "-", assetAddressString()));
-    }
-
-    // https://ethereum.stackexchange.com/a/40977
-    function uintToString(uint _i) internal pure returns (string memory _uintAsString) {
-        if (_i == 0) {
-            return "0";
-        }
-        uint j = _i;
-        uint len;
-        while (j != 0) {
-            len++;
-            j /= 10;
-        }
-        bytes memory bstr = new bytes(len);
-        uint k = len - 1;
-        while (_i != 0) {
-            bstr[k--] = byte(uint8(48 + _i % 10));
-            _i /= 10;
-        }
-        return string(bstr);
-    }
-
-    // https://ethereum.stackexchange.com/a/58341
-    function assetAddressString() private view returns (string memory) {
-        bytes memory alphabet = "0123456789abcdef";
-        bytes32 data = assetAddress;
-
-        bytes memory str = new bytes(2 + data.length * 2);
-        for (uint i = 0; i < data.length; i++) {
-            str[i * 2] = alphabet[uint(uint8(data[i] >> 4))];
-            str[1 + i * 2] = alphabet[uint(uint8(data[i] & 0x0f))];
-        }
-        return string(str);
-    }
-
-    /**
-     * @dev Returns the symbol of the token, usually a shorter version of the
-     * name.
-     */
-    function symbol() public view returns (string memory) {
-        return _symbol;
-    }
-
-    /**
-     * @dev Returns the number of decimals used to get its user representation.
-     * For example, if `decimals` equals `2`, a balance of `505` tokens should
-     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
-     *
-     * Tokens usually opt for a value of 18, imitating the relationship between
-     * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
-     * called.
-     *
-     * NOTE: This information is only used for _display_ purposes: it in
-     * no way affects any of the arithmetic of the contract, including
-     * {IERC20-balanceOf} and {IERC20-transfer}.
-     */
-    function decimals() public view returns (uint8) {
-        return _decimals;
-    }
-
-    /**
-     * @dev See {IERC20-totalSupply}.
-     */
-    function totalSupply() public view override returns (uint256) {
-        return _totalSupply;
-    }
-
-    /**
-     * @dev See {IERC20-balanceOf}.
-     */
-    function balanceOf(address account) public view override returns (uint256) {
-        return _balances[account];
-    }
-
-    /**
-     * @dev See {IERC20-transfer}.
-     *
-     * Requirements:
-     *
-     * - `recipient` cannot be the zero address.
-     * - the caller must have a balance of at least `amount`.
-     */
-    function transfer(address recipient, uint256 amount) public override returns (bool) {
-        _transfer(_msgSender(), recipient, amount);
-        return true;
-    }
-
-    /**
-     * @dev See {IERC20-allowance}.
-     */
-    function allowance(address owner, address spender) public view override returns (uint256) {
-        return _allowances[owner][spender];
-    }
-
-    /**
-     * @dev See {IERC20-approve}.
-     *
-     * Requirements:
-     *
-     * - `spender` cannot be the zero address.
-     */
-    function approve(address spender, uint256 amount) public override returns (bool) {
-        _approve(_msgSender(), spender, amount);
-        return true;
-    }
-
-    /**
-     * @dev See {IERC20-transferFrom}.
-     *
-     * Emits an {Approval} event indicating the updated allowance. This is not
-     * required by the EIP. See the note at the beginning of {ERC20};
-     *
-     * Requirements:
-     * - `sender` and `recipient` cannot be the zero address.
-     * - `sender` must have a balance of at least `amount`.
-     * - the caller must have allowance for ``sender``'s tokens of at least
-     * `amount`.
-     */
-    function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
-        _transfer(sender, recipient, amount);
-        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
-        return true;
-    }
-
-    /**
-     * @dev Atomically increases the allowance granted to `spender` by the caller.
-     *
-     * This is an alternative to {approve} that can be used as a mitigation for
-     * problems described in {IERC20-approve}.
-     *
-     * Emits an {Approval} event indicating the updated allowance.
-     *
-     * Requirements:
-     *
-     * - `spender` cannot be the zero address.
-     */
-    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
-        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
-        return true;
-    }
-
-    /**
-     * @dev Atomically decreases the allowance granted to `spender` by the caller.
-     *
-     * This is an alternative to {approve} that can be used as a mitigation for
-     * problems described in {IERC20-approve}.
-     *
-     * Emits an {Approval} event indicating the updated allowance.
-     *
-     * Requirements:
-     *
-     * - `spender` cannot be the zero address.
-     * - `spender` must have allowance for the caller of at least
-     * `subtractedValue`.
-     */
-    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
-        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
-        return true;
-    }
-
-    /**
-     * @dev Moves tokens `amount` from `sender` to `recipient`.
-     *
-     * This is internal function is equivalent to {transfer}, and can be used to
-     * e.g. implement automatic token fees, slashing mechanisms, etc.
-     *
-     * Emits a {Transfer} event.
-     *
-     * Requirements:
-     *
-     * - `sender` cannot be the zero address.
-     * - `recipient` cannot be the zero address.
-     * - `sender` must have a balance of at least `amount`.
-     */
-    function _transfer(address sender, address recipient, uint256 amount) internal {
-        require(sender != address(0), "ERC20: transfer from the zero address");
-        require(recipient != address(0), "ERC20: transfer to the zero address");
-
-        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
-        _balances[recipient] = _balances[recipient].add(amount);
-        emit Transfer(sender, recipient, amount);
-    }
-
-    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
-     * the total supply.
-     *
-     * Emits a {Transfer} event with `from` set to the zero address.
-     *
-     * Requirements
-     *
-     * - `to` cannot be the zero address.
-     */
-    function _mint(address account, uint256 amount) internal {
-        require(account != address(0), "ERC20: mint to the zero address");
-
-        _totalSupply = _totalSupply.add(amount);
-        _balances[account] = _balances[account].add(amount);
-        emit Transfer(address(0), account, amount);
-    }
-
-    /**
-     * @dev Destroys `amount` tokens from `account`, reducing the
-     * total supply.
-     *
-     * Emits a {Transfer} event with `to` set to the zero address.
-     *
-     * Requirements
-     *
-     * - `account` cannot be the zero address.
-     * - `account` must have at least `amount` tokens.
-     */
-    function _burn(address account, uint256 amount) internal {
-        require(account != address(0), "ERC20: burn from the zero address");
-
-        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
-        _totalSupply = _totalSupply.sub(amount);
-        emit Transfer(account, address(0), amount);
-    }
-
-    /**
-     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
-     *
-     * This is internal function is equivalent to `approve`, and can be used to
-     * e.g. set automatic allowances for certain subsystems, etc.
-     *
-     * Emits an {Approval} event.
-     *
-     * Requirements:
-     *
-     * - `owner` cannot be the zero address.
-     * - `spender` cannot be the zero address.
-     */
-    function _approve(address owner, address spender, uint256 amount) internal {
-        require(owner != address(0), "ERC20: approve from the zero address");
-        require(spender != address(0), "ERC20: approve to the zero address");
-
-        _allowances[owner][spender] = amount;
-        emit Approval(owner, spender, amount);
-    }
-
-    /**
-     * @dev Sets {decimals} to a value other than the default one of 18.
-     *
-     * WARNING: This function should only be called from the constructor. Most
-     * applications that interact with token contracts will not expect
-     * {decimals} to ever change, and may work incorrectly if it does.
-     */
-    function _setupDecimals(uint8 decimals_) internal {
-        _decimals = decimals_;
-    }
-}

+ 167 - 163
ethereum/contracts/BytesLib.sol → ethereum/contracts/libraries/external/BytesLib.sol

@@ -6,7 +6,7 @@
  * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
  * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
  *      The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
  *      The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
  */
  */
-pragma solidity >=0.5.0 <0.7.0;
+pragma solidity >=0.8.0 <0.9.0;
 
 
 
 
 library BytesLib {
 library BytesLib {
@@ -14,55 +14,55 @@ library BytesLib {
         bytes memory _preBytes,
         bytes memory _preBytes,
         bytes memory _postBytes
         bytes memory _postBytes
     )
     )
-    internal
-    pure
-    returns (bytes memory)
+        internal
+        pure
+        returns (bytes memory)
     {
     {
         bytes memory tempBytes;
         bytes memory tempBytes;
 
 
         assembly {
         assembly {
-        // Get a location of some free memory and store it in tempBytes as
-        // Solidity does for memory variables.
+            // Get a location of some free memory and store it in tempBytes as
+            // Solidity does for memory variables.
             tempBytes := mload(0x40)
             tempBytes := mload(0x40)
 
 
-        // Store the length of the first bytes array at the beginning of
-        // the memory for tempBytes.
+            // Store the length of the first bytes array at the beginning of
+            // the memory for tempBytes.
             let length := mload(_preBytes)
             let length := mload(_preBytes)
             mstore(tempBytes, length)
             mstore(tempBytes, length)
 
 
-        // Maintain a memory counter for the current write location in the
-        // temp bytes array by adding the 32 bytes for the array length to
-        // the starting location.
+            // Maintain a memory counter for the current write location in the
+            // temp bytes array by adding the 32 bytes for the array length to
+            // the starting location.
             let mc := add(tempBytes, 0x20)
             let mc := add(tempBytes, 0x20)
-        // Stop copying when the memory counter reaches the length of the
-        // first bytes array.
+            // Stop copying when the memory counter reaches the length of the
+            // first bytes array.
             let end := add(mc, length)
             let end := add(mc, length)
 
 
             for {
             for {
-            // Initialize a copy counter to the start of the _preBytes data,
-            // 32 bytes into its memory.
+                // Initialize a copy counter to the start of the _preBytes data,
+                // 32 bytes into its memory.
                 let cc := add(_preBytes, 0x20)
                 let cc := add(_preBytes, 0x20)
             } lt(mc, end) {
             } lt(mc, end) {
-            // Increase both counters by 32 bytes each iteration.
+                // Increase both counters by 32 bytes each iteration.
                 mc := add(mc, 0x20)
                 mc := add(mc, 0x20)
                 cc := add(cc, 0x20)
                 cc := add(cc, 0x20)
             } {
             } {
-            // Write the _preBytes data into the tempBytes memory 32 bytes
-            // at a time.
+                // Write the _preBytes data into the tempBytes memory 32 bytes
+                // at a time.
                 mstore(mc, mload(cc))
                 mstore(mc, mload(cc))
             }
             }
 
 
-        // Add the length of _postBytes to the current length of tempBytes
-        // and store it as the new length in the first 32 bytes of the
-        // tempBytes memory.
+            // Add the length of _postBytes to the current length of tempBytes
+            // and store it as the new length in the first 32 bytes of the
+            // tempBytes memory.
             length := mload(_postBytes)
             length := mload(_postBytes)
             mstore(tempBytes, add(length, mload(tempBytes)))
             mstore(tempBytes, add(length, mload(tempBytes)))
 
 
-        // Move the memory counter back from a multiple of 0x20 to the
-        // actual end of the _preBytes data.
+            // Move the memory counter back from a multiple of 0x20 to the
+            // actual end of the _preBytes data.
             mc := end
             mc := end
-        // Stop copying when the memory counter reaches the new combined
-        // length of the arrays.
+            // Stop copying when the memory counter reaches the new combined
+            // length of the arrays.
             end := add(mc, length)
             end := add(mc, length)
 
 
             for {
             for {
@@ -74,14 +74,14 @@ library BytesLib {
                 mstore(mc, mload(cc))
                 mstore(mc, mload(cc))
             }
             }
 
 
-        // Update the free-memory pointer by padding our last write location
-        // to 32 bytes: add 31 bytes to the end of tempBytes to move to the
-        // next 32 byte block, then round down to the nearest multiple of
-        // 32. If the sum of the length of the two arrays is zero then add
-        // one before rounding down to leave a blank 32 bytes (the length block with 0).
+            // Update the free-memory pointer by padding our last write location
+            // to 32 bytes: add 31 bytes to the end of tempBytes to move to the
+            // next 32 byte block, then round down to the nearest multiple of
+            // 32. If the sum of the length of the two arrays is zero then add
+            // one before rounding down to leave a blank 32 bytes (the length block with 0).
             mstore(0x40, and(
             mstore(0x40, and(
-            add(add(end, iszero(add(length, mload(_preBytes)))), 31),
-            not(31) // Round down to the nearest 32 bytes.
+              add(add(end, iszero(add(length, mload(_preBytes)))), 31),
+              not(31) // Round down to the nearest 32 bytes.
             ))
             ))
         }
         }
 
 
@@ -90,73 +90,73 @@ library BytesLib {
 
 
     function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
     function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
         assembly {
         assembly {
-        // Read the first 32 bytes of _preBytes storage, which is the length
-        // of the array. (We don't need to use the offset into the slot
-        // because arrays use the entire slot.)
-            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.
+            // Read the first 32 bytes of _preBytes storage, which is the length
+            // of the array. (We don't need to use the offset into the slot
+            // because arrays use the entire slot.)
+            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 slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
             let mlength := mload(_postBytes)
             let mlength := mload(_postBytes)
             let newlength := add(slength, mlength)
             let newlength := add(slength, mlength)
-        // slength can contain both the length and contents of the array
-        // if length < 32 bytes so let's prepare for that
-        // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
+            // slength can contain both the length and contents of the array
+            // if length < 32 bytes so let's prepare for that
+            // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
             switch add(lt(slength, 32), lt(newlength, 32))
             switch add(lt(slength, 32), lt(newlength, 32))
             case 2 {
             case 2 {
-            // Since the new array still fits in the slot, we just need to
-            // update the contents of the slot.
-            // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
+                // Since the new array still fits in the slot, we just need to
+                // update the contents of the slot.
+                // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
                 sstore(
                 sstore(
-                _preBytes_slot,
-                // all the modifications to the slot are inside this
-                // next block
-                add(
-                // we can just add to the slot contents because the
-                // bytes we want to change are the LSBs
-                fslot,
-                add(
-                mul(
-                div(
-                // load the bytes from memory
-                mload(add(_postBytes, 0x20)),
-                // zero all bytes to the right
-                exp(0x100, sub(32, mlength))
-                ),
-                // and now shift left the number of bytes to
-                // leave space for the length in the slot
-                exp(0x100, sub(32, newlength))
-                ),
-                // increase length by the double of the memory
-                // bytes length
-                mul(mlength, 2)
-                )
-                )
+                    _preBytes.slot,
+                    // all the modifications to the slot are inside this
+                    // next block
+                    add(
+                        // we can just add to the slot contents because the
+                        // bytes we want to change are the LSBs
+                        fslot,
+                        add(
+                            mul(
+                                div(
+                                    // load the bytes from memory
+                                    mload(add(_postBytes, 0x20)),
+                                    // zero all bytes to the right
+                                    exp(0x100, sub(32, mlength))
+                                ),
+                                // and now shift left the number of bytes to
+                                // leave space for the length in the slot
+                                exp(0x100, sub(32, newlength))
+                            ),
+                            // increase length by the double of the memory
+                            // bytes length
+                            mul(mlength, 2)
+                        )
+                    )
                 )
                 )
             }
             }
             case 1 {
             case 1 {
-            // The stored value fits in the slot, but the combined value
-            // will exceed it.
-            // get the keccak hash to get the contents of the array
-                mstore(0x0, _preBytes_slot)
+                // The stored value fits in the slot, but the combined value
+                // will exceed it.
+                // get the keccak hash to get the contents of the array
+                mstore(0x0, _preBytes.slot)
                 let sc := add(keccak256(0x0, 0x20), div(slength, 32))
                 let sc := add(keccak256(0x0, 0x20), div(slength, 32))
 
 
-            // save new length
-                sstore(_preBytes_slot, add(mul(newlength, 2), 1))
+                // save new length
+                sstore(_preBytes.slot, add(mul(newlength, 2), 1))
 
 
-            // The contents of the _postBytes array start 32 bytes into
-            // the structure. Our first read should obtain the `submod`
-            // bytes that can fit into the unused space in the last word
-            // of the stored array. To get this, we read 32 bytes starting
-            // from `submod`, so the data we read overlaps with the array
-            // contents by `submod` bytes. Masking the lowest-order
-            // `submod` bytes allows us to add that value directly to the
-            // stored value.
+                // The contents of the _postBytes array start 32 bytes into
+                // the structure. Our first read should obtain the `submod`
+                // bytes that can fit into the unused space in the last word
+                // of the stored array. To get this, we read 32 bytes starting
+                // from `submod`, so the data we read overlaps with the array
+                // contents by `submod` bytes. Masking the lowest-order
+                // `submod` bytes allows us to add that value directly to the
+                // stored value.
 
 
                 let submod := sub(32, slength)
                 let submod := sub(32, slength)
                 let mc := add(_postBytes, submod)
                 let mc := add(_postBytes, submod)
@@ -164,14 +164,14 @@ library BytesLib {
                 let mask := sub(exp(0x100, submod), 1)
                 let mask := sub(exp(0x100, submod), 1)
 
 
                 sstore(
                 sstore(
-                sc,
-                add(
-                and(
-                fslot,
-                0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
-                ),
-                and(mload(mc), mask)
-                )
+                    sc,
+                    add(
+                        and(
+                            fslot,
+                            0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
+                        ),
+                        and(mload(mc), mask)
+                    )
                 )
                 )
 
 
                 for {
                 for {
@@ -189,16 +189,16 @@ library BytesLib {
                 sstore(sc, mul(div(mload(mc), mask), mask))
                 sstore(sc, mul(div(mload(mc), mask), mask))
             }
             }
             default {
             default {
-            // get the keccak hash to get the contents of the array
-                mstore(0x0, _preBytes_slot)
-            // Start copying to the last used word of the stored array.
+                // get the keccak hash to get the contents of the array
+                mstore(0x0, _preBytes.slot)
+                // Start copying to the last used word of the stored array.
                 let sc := add(keccak256(0x0, 0x20), div(slength, 32))
                 let sc := add(keccak256(0x0, 0x20), div(slength, 32))
 
 
-            // save new length
-                sstore(_preBytes_slot, add(mul(newlength, 2), 1))
+                // save new length
+                sstore(_preBytes.slot, add(mul(newlength, 2), 1))
 
 
-            // Copy over the first `submod` bytes of the new data as in
-            // case 1 above.
+                // Copy over the first `submod` bytes of the new data as in
+                // case 1 above.
                 let slengthmod := mod(slength, 32)
                 let slengthmod := mod(slength, 32)
                 let mlengthmod := mod(mlength, 32)
                 let mlengthmod := mod(mlength, 32)
                 let submod := sub(32, slengthmod)
                 let submod := sub(32, slengthmod)
@@ -230,41 +230,42 @@ library BytesLib {
         uint256 _start,
         uint256 _start,
         uint256 _length
         uint256 _length
     )
     )
-    internal
-    pure
-    returns (bytes memory)
+        internal
+        pure
+        returns (bytes memory)
     {
     {
-        require(_bytes.length >= (_start + _length), "Read out of bounds");
+        require(_length + 31 >= _length, "slice_overflow");
+        require(_bytes.length >= _start + _length, "slice_outOfBounds");
 
 
         bytes memory tempBytes;
         bytes memory tempBytes;
 
 
         assembly {
         assembly {
             switch iszero(_length)
             switch iszero(_length)
             case 0 {
             case 0 {
-            // Get a location of some free memory and store it in tempBytes as
-            // Solidity does for memory variables.
+                // Get a location of some free memory and store it in tempBytes as
+                // Solidity does for memory variables.
                 tempBytes := mload(0x40)
                 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.
+                // The first word of the slice result is potentially a partial
+                // word read from the original array. To read it, we calculate
+                // the length of that partial word and start copying that many
+                // bytes into the array. The first word we copy will start with
+                // data we don't care about, but the last `lengthmod` bytes will
+                // land at the beginning of the contents of the new array. When
+                // we're done copying, we overwrite the full first word with
+                // the actual length of the slice.
                 let lengthmod := and(_length, 31)
                 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.
+                // 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 mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
                 let end := add(mc, _length)
                 let end := add(mc, _length)
 
 
                 for {
                 for {
-                // The multiplication in the next line has the same exact purpose
-                // as the one above.
+                    // 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)
                     let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
                 } lt(mc, end) {
                 } lt(mc, end) {
                     mc := add(mc, 0x20)
                     mc := add(mc, 0x20)
@@ -275,13 +276,16 @@ library BytesLib {
 
 
                 mstore(tempBytes, _length)
                 mstore(tempBytes, _length)
 
 
-            //update free-memory pointer
-            //allocating the array padded to 32 bytes like the compiler does now
+                //update free-memory pointer
+                //allocating the array padded to 32 bytes like the compiler does now
                 mstore(0x40, and(add(mc, 31), not(31)))
                 mstore(0x40, and(add(mc, 31), not(31)))
             }
             }
             //if we want a zero-length slice let's just return a zero-length array
             //if we want a zero-length slice let's just return a zero-length array
             default {
             default {
                 tempBytes := mload(0x40)
                 tempBytes := mload(0x40)
+                //zero out the 32 bytes slice we are about to return
+                //we need to do it because Solidity does not garbage collect
+                mstore(tempBytes, 0)
 
 
                 mstore(0x40, add(tempBytes, 0x20))
                 mstore(0x40, add(tempBytes, 0x20))
             }
             }
@@ -291,7 +295,7 @@ library BytesLib {
     }
     }
 
 
     function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
     function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
-        require(_bytes.length >= (_start + 20), "Read out of bounds");
+        require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
         address tempAddress;
         address tempAddress;
 
 
         assembly {
         assembly {
@@ -302,7 +306,7 @@ library BytesLib {
     }
     }
 
 
     function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
     function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
-        require(_bytes.length >= (_start + 1), "Read out of bounds");
+        require(_bytes.length >= _start + 1 , "toUint8_outOfBounds");
         uint8 tempUint;
         uint8 tempUint;
 
 
         assembly {
         assembly {
@@ -313,7 +317,7 @@ library BytesLib {
     }
     }
 
 
     function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {
     function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {
-        require(_bytes.length >= (_start + 2), "Read out of bounds");
+        require(_bytes.length >= _start + 2, "toUint16_outOfBounds");
         uint16 tempUint;
         uint16 tempUint;
 
 
         assembly {
         assembly {
@@ -324,7 +328,7 @@ library BytesLib {
     }
     }
 
 
     function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {
     function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {
-        require(_bytes.length >= (_start + 4), "Read out of bounds");
+        require(_bytes.length >= _start + 4, "toUint32_outOfBounds");
         uint32 tempUint;
         uint32 tempUint;
 
 
         assembly {
         assembly {
@@ -335,7 +339,7 @@ library BytesLib {
     }
     }
 
 
     function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {
     function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {
-        require(_bytes.length >= (_start + 8), "Read out of bounds");
+        require(_bytes.length >= _start + 8, "toUint64_outOfBounds");
         uint64 tempUint;
         uint64 tempUint;
 
 
         assembly {
         assembly {
@@ -346,7 +350,7 @@ library BytesLib {
     }
     }
 
 
     function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {
     function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {
-        require(_bytes.length >= (_start + 12), "Read out of bounds");
+        require(_bytes.length >= _start + 12, "toUint96_outOfBounds");
         uint96 tempUint;
         uint96 tempUint;
 
 
         assembly {
         assembly {
@@ -357,7 +361,7 @@ library BytesLib {
     }
     }
 
 
     function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {
     function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {
-        require(_bytes.length >= (_start + 16), "Read out of bounds");
+        require(_bytes.length >= _start + 16, "toUint128_outOfBounds");
         uint128 tempUint;
         uint128 tempUint;
 
 
         assembly {
         assembly {
@@ -368,7 +372,7 @@ library BytesLib {
     }
     }
 
 
     function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {
     function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {
-        require(_bytes.length >= (_start + 32), "Read out of bounds");
+        require(_bytes.length >= _start + 32, "toUint256_outOfBounds");
         uint256 tempUint;
         uint256 tempUint;
 
 
         assembly {
         assembly {
@@ -379,7 +383,7 @@ library BytesLib {
     }
     }
 
 
     function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {
     function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {
-        require(_bytes.length >= (_start + 32), "Read out of bounds");
+        require(_bytes.length >= _start + 32, "toBytes32_outOfBounds");
         bytes32 tempBytes32;
         bytes32 tempBytes32;
 
 
         assembly {
         assembly {
@@ -395,13 +399,13 @@ library BytesLib {
         assembly {
         assembly {
             let length := mload(_preBytes)
             let length := mload(_preBytes)
 
 
-        // if lengths don't match the arrays are not equal
+            // if lengths don't match the arrays are not equal
             switch eq(length, mload(_postBytes))
             switch eq(length, mload(_postBytes))
             case 1 {
             case 1 {
-            // 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
+                // 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
                 let cb := 1
 
 
                 let mc := add(_preBytes, 0x20)
                 let mc := add(_preBytes, 0x20)
@@ -415,16 +419,16 @@ library BytesLib {
                     mc := add(mc, 0x20)
                     mc := add(mc, 0x20)
                     cc := add(cc, 0x20)
                     cc := add(cc, 0x20)
                 } {
                 } {
-                // if any of these checks fails then arrays are not equal
+                    // if any of these checks fails then arrays are not equal
                     if iszero(eq(mload(mc), mload(cc))) {
                     if iszero(eq(mload(mc), mload(cc))) {
-                    // unsuccess:
+                        // unsuccess:
                         success := 0
                         success := 0
                         cb := 0
                         cb := 0
                     }
                     }
                 }
                 }
             }
             }
             default {
             default {
-            // unsuccess:
+                // unsuccess:
                 success := 0
                 success := 0
             }
             }
         }
         }
@@ -436,58 +440,58 @@ library BytesLib {
         bytes storage _preBytes,
         bytes storage _preBytes,
         bytes memory _postBytes
         bytes memory _postBytes
     )
     )
-    internal
-    view
-    returns (bool)
+        internal
+        view
+        returns (bool)
     {
     {
         bool success = true;
         bool success = true;
 
 
         assembly {
         assembly {
-        // we know _preBytes_offset is 0
-            let fslot := sload(_preBytes_slot)
-        // Decode the length of the stored array like in concatStorage().
+            // we know _preBytes_offset is 0
+            let fslot := sload(_preBytes.slot)
+            // Decode the length of the stored array like in concatStorage().
             let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
             let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
             let mlength := mload(_postBytes)
             let mlength := mload(_postBytes)
 
 
-        // if lengths don't match the arrays are not equal
+            // if lengths don't match the arrays are not equal
             switch eq(slength, mlength)
             switch eq(slength, mlength)
             case 1 {
             case 1 {
-            // slength can contain both the length and contents of the array
-            // if length < 32 bytes so let's prepare for that
-            // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
+                // slength can contain both the length and contents of the array
+                // if length < 32 bytes so let's prepare for that
+                // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
                 if iszero(iszero(slength)) {
                 if iszero(iszero(slength)) {
                     switch lt(slength, 32)
                     switch lt(slength, 32)
                     case 1 {
                     case 1 {
-                    // blank the last byte which is the length
+                        // blank the last byte which is the length
                         fslot := mul(div(fslot, 0x100), 0x100)
                         fslot := mul(div(fslot, 0x100), 0x100)
 
 
                         if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
                         if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
-                        // unsuccess:
+                            // unsuccess:
                             success := 0
                             success := 0
                         }
                         }
                     }
                     }
                     default {
                     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
+                        // 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
                         let cb := 1
 
 
-                    // get the keccak hash to get the contents of the array
-                        mstore(0x0, _preBytes_slot)
+                        // get the keccak hash to get the contents of the array
+                        mstore(0x0, _preBytes.slot)
                         let sc := keccak256(0x0, 0x20)
                         let sc := keccak256(0x0, 0x20)
 
 
                         let mc := add(_postBytes, 0x20)
                         let mc := add(_postBytes, 0x20)
                         let end := add(mc, mlength)
                         let end := add(mc, mlength)
 
 
-                    // the next line is the loop condition:
-                    // while(uint256(mc < end) + cb == 2)
+                        // the next line is the loop condition:
+                        // while(uint256(mc < end) + cb == 2)
                         for {} eq(add(lt(mc, end), cb), 2) {
                         for {} eq(add(lt(mc, end), cb), 2) {
                             sc := add(sc, 1)
                             sc := add(sc, 1)
                             mc := add(mc, 0x20)
                             mc := add(mc, 0x20)
                         } {
                         } {
                             if iszero(eq(sload(sc), mload(mc))) {
                             if iszero(eq(sload(sc), mload(mc))) {
-                            // unsuccess:
+                                // unsuccess:
                                 success := 0
                                 success := 0
                                 cb := 0
                                 cb := 0
                             }
                             }
@@ -496,7 +500,7 @@ library BytesLib {
                 }
                 }
             }
             }
             default {
             default {
-            // unsuccess:
+                // unsuccess:
                 success := 0
                 success := 0
             }
             }
         }
         }

+ 16 - 0
ethereum/contracts/mock/MockImplementation.sol

@@ -0,0 +1,16 @@
+// contracts/Implementation.sol
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+import "../Implementation.sol";
+
+contract MockImplementation is Implementation {
+    function initialize() initializer public {
+        // this function needs to be exposed for an upgrade to pass
+    }
+
+    function testNewImplementationActive() external pure returns (bool) {
+        return true;
+    }
+}

+ 5 - 11
ethereum/migrations/1_initial_migration.js

@@ -1,12 +1,6 @@
-const WrappedAsset = artifacts.require("WrappedAsset");
-const Wormhole = artifacts.require("Wormhole");
-const ERC20 = artifacts.require("ERC20PresetMinterPauser");
+var Migrations = artifacts.require("Migrations");
 
 
-module.exports = async function (deployer) {
-    await deployer.deploy(WrappedAsset);
-    await deployer.deploy(Wormhole, {
-        keys: ["0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"],
-        expiration_time: 0
-    }, WrappedAsset.address, 1000);
-    await deployer.deploy(ERC20, "Test Token","TKN");
-};
+module.exports = function(deployer) {
+    // Deploy the Migrations contract as our only task
+    deployer.deploy(Migrations);
+};

+ 0 - 14
ethereum/migrations/2_create_token.js

@@ -1,14 +0,0 @@
-const WrappedAsset = artifacts.require("WrappedAsset");
-const Wormhole = artifacts.require("Wormhole");
-const ERC20 = artifacts.require("ERC20PresetMinterPauser");
-
-module.exports = async function (deployer) {
-    let bridge = await Wormhole.deployed();
-    let token = await ERC20.deployed();
-
-    console.log("Token:", token.address);
-
-    // Create example ERC20 and mint a generous amount of it.
-    await token.mint("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1", "1000000000000000000");
-    await token.approve(bridge.address, "1000000000000000000");
-};

+ 30 - 0
ethereum/migrations/2_deploy_wormhole.js

@@ -0,0 +1,30 @@
+const Implementation = artifacts.require("Implementation");
+const Wormhole = artifacts.require("Wormhole");
+
+const initialSigners = [
+    // testSigner 1 & 2
+    "0x7b6FA3F2bEb40eAf9Cefcb20505163C70d76f21c",
+    "0x4ba0C2db9A26208b3bB1a50B01b16941c10D76db",
+]
+const chainId = "0x2";
+const governanceChainId = "0x3";
+const governanceContract = "0x0000000000000000000000000000000000000000000000000000000000000004"; // bytes32
+
+module.exports = async function (deployer) {
+    // deploy implementation
+    await deployer.deploy(Implementation);
+
+    // encode initialisation data
+    const impl = new web3.eth.Contract(Implementation.abi, Implementation.address);
+    const initData = impl.methods.initialize(
+        initialSigners,
+        chainId,
+        governanceChainId,
+        governanceContract
+    ).encodeABI();
+
+    // console.log(initData)
+
+    // deploy proxy
+    await deployer.deploy(Wormhole, Implementation.address, initData);
+};

Datei-Diff unterdrückt, da er zu groß ist
+ 8412 - 7849
ethereum/package-lock.json


+ 6 - 3
ethereum/package.json

@@ -6,13 +6,13 @@
   "devDependencies": {
   "devDependencies": {
     "@chainsafe/truffle-plugin-abigen": "0.0.1",
     "@chainsafe/truffle-plugin-abigen": "0.0.1",
     "@openzeppelin/cli": "^2.8.2",
     "@openzeppelin/cli": "^2.8.2",
-    "@openzeppelin/contracts": "^3.2.0",
+    "@openzeppelin/contracts": "^4.1.0",
     "@openzeppelin/test-environment": "^0.1.6",
     "@openzeppelin/test-environment": "^0.1.6",
     "@openzeppelin/test-helpers": "^0.5.9",
     "@openzeppelin/test-helpers": "^0.5.9",
     "@truffle/hdwallet-provider": "^1.2.0",
     "@truffle/hdwallet-provider": "^1.2.0",
     "chai": "^4.2.0",
     "chai": "^4.2.0",
     "mocha": "^8.2.1",
     "mocha": "^8.2.1",
-    "truffle": "^5.1.53",
+    "truffle": "^5.3.6",
     "truffle-assertions": "^0.9.2"
     "truffle-assertions": "^0.9.2"
   },
   },
   "scripts": {
   "scripts": {
@@ -23,6 +23,9 @@
   "author": "",
   "author": "",
   "license": "ISC",
   "license": "ISC",
   "dependencies": {
   "dependencies": {
-    "ganache-cli": "^6.12.1"
+    "elliptic": "^6.5.2",
+    "ganache-cli": "^6.12.1",
+    "jsonfile": "^4.0.0",
+    "solc": "^0.8.4"
   }
   }
 }
 }

+ 570 - 205
ethereum/test/wormhole.js

@@ -1,6 +1,16 @@
+const jsonfile = require('jsonfile');
+const elliptic = require('elliptic');
+const path = require('path');
+
 const Wormhole = artifacts.require("Wormhole");
 const Wormhole = artifacts.require("Wormhole");
-const WrappedAsset = artifacts.require("WrappedAsset");
-const ERC20 = artifacts.require("ERC20PresetMinterPauser");
+const MockImplementation = artifacts.require("MockImplementation");
+const Implementation = artifacts.require("Implementation");
+
+const testSigner1PK = "13b422ac887f1912629e34928674cb4a81e59d96a4d74653e41c2a305ba754a5";
+const testSigner2PK = "892330666a850761e7370376430bb8c2aa1494072d3bfeaed0c4fa3d5a9135fe";
+const testSigner3PK = "87b45997ea577b93073568f06fc4838cffc1d01f90fc4d57f936957f3c4d99fb";
+
+const ImplementationFullABI = jsonfile.readFileSync("build/contracts/Implementation.json").abi
 
 
 // Taken from https://medium.com/fluidity/standing-the-time-of-test-b906fcc374a9
 // Taken from https://medium.com/fluidity/standing-the-time-of-test-b906fcc374a9
 advanceTimeAndBlock = async (time) => {
 advanceTimeAndBlock = async (time) => {
@@ -43,247 +53,602 @@ advanceBlock = () => {
     });
     });
 }
 }
 
 
-/*
-    The VAA test fixtures are generated by bridge/cmd/vaa-test.
- */
-
 contract("Wormhole", function () {
 contract("Wormhole", function () {
-    it("should use master wrapped asset", async function () {
-        let bridge = await Wormhole.deployed();
-        let wa = await bridge.wrappedAssetMaster.call();
-        assert.equal(wa, WrappedAsset.address)
-    });
-
-    it("should transfer tokens in on valid VAA", async function () {
-        let bridge = await Wormhole.deployed();
-
-        // User locked an asset on the foreign chain and the VAA proving this is transferred in.
-        await bridge.submitVAA("0x01000000000100454e7de661cd4386b1ce598a505825f8ed66fbc6a608393bae6257fef7370da27a2068240a902470bed6c0b1fa23d38e5d5958e2a422d59a0217fbe155638ed600000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
-        // Expect user to have a balance of a new wrapped asset
-        // submitVAA has automatically created a new WrappedAsset for the foreign asset that has been transferred in.
-        // We know the address because deterministic network. A user would see the address in the submitVAA tx log.
-        let wa = new WrappedAsset("0xC3697aaf5B3D354214548248710414812099bc93");
-        assert.equal(await wa.assetChain(), 1)
-        // Remote asset's contract address.
-        assert.equal(await wa.assetAddress(), "0x0000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988")
-        // Account that the user requests the transfer to.
-        let balance = await wa.balanceOf("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1");
-        assert.equal(balance, "1000000000000000000");
-    });
-
-    it("should not accept the same VAA twice", async function () {
-        let bridge = await Wormhole.deployed();
-        try {
-            await bridge.submitVAA("0x01000000000100454e7de661cd4386b1ce598a505825f8ed66fbc6a608393bae6257fef7370da27a2068240a902470bed6c0b1fa23d38e5d5958e2a422d59a0217fbe155638ed600000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000");
-        } catch (e) {
-            assert.equal(e.reason, "VAA was already executed")
+    const testSigner1 = web3.eth.accounts.privateKeyToAccount(testSigner1PK);
+    const testSigner2 = web3.eth.accounts.privateKeyToAccount(testSigner2PK);
+    const testSigner3 = web3.eth.accounts.privateKeyToAccount(testSigner3PK);
+    const testChainId = "2";
+    const testGovernanceChainId = "3";
+    const testGovernanceContract = "0x0000000000000000000000000000000000000000000000000000000000000004";
+
+    it("should be initialized with the correct signers and values", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+
+        const index = await initialized.methods.getCurrentGuardianSetIndex().call();
+        const set = (await initialized.methods.getGuardianSet(index).call());
+
+        // check set
+        assert.lengthOf(set, 2);
+        assert.equal(set[0][0], testSigner1.address);
+        assert.equal(set[0][1], testSigner2.address);
+
+        // check expiration
+        assert.equal(set.expirationTime, "0");
+
+        // chain id
+        const chainId = await initialized.methods.chainId().call();
+        assert.equal(chainId, testChainId);
+
+        // governance
+        const governanceChainId = await initialized.methods.governanceChainId().call();
+        assert.equal(governanceChainId, testGovernanceChainId);
+        const governanceContract = await initialized.methods.governanceContract().call();
+        assert.equal(governanceContract, testGovernanceContract);
+    })
+
+    it("initialize should be non-reentrant", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+
+        try{
+            await initialized.methods.initialize([
+                testSigner1.address
+            ], testChainId, testGovernanceChainId, testGovernanceContract).estimateGas();
+        }  catch (error) {
+            assert.equal(error.message, "Returned error: VM Exception while processing transaction: revert already initialized")
             return
             return
         }
         }
+
         assert.fail("did not fail")
         assert.fail("did not fail")
-    });
+    })
+
+    it("should log a published message correctly", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+        const accounts = await web3.eth.getAccounts();
+
+        const log = await initialized.methods.publishMessage(
+            "0x123",
+            "0x123321",
+            false
+        ).send({
+            value : 0, // fees are set to 0 initially
+            from : accounts[0]
+        })
+
+        assert.equal(log.events.LogMessagePublished.returnValues.sender.toString(), accounts[0]);
+        assert.equal(log.events.LogMessagePublished.returnValues.nonce, 291);
+        assert.equal(log.events.LogMessagePublished.returnValues.payload.toString(), "0x123321");
+    })
+
+    it("parses VMs correctly", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+
+        const timestamp = 1000;
+        const nonce = 1001;
+        const emitterChainId = 11;
+        const emitterAddress = "0x0000000000000000000000000000000000000000000000000000000000000eee"
+        const data = "0xaaaaaa";
+
+        const vm = await signAndEncodeVM(
+            timestamp,
+            nonce,
+            emitterChainId,
+            emitterAddress,
+            data,
+            [
+                testSigner1PK,
+                testSigner2PK
+            ],
+            0
+        );
+
+        let result
+        try {
+            result = await initialized.methods.parseAndVerifyVM("0x" + vm).call();
+        } catch(err) {
+            console.log(err)
+            assert.fail("parseAndVerifyVM failed")
+        }
 
 
-    it("should burn tokens on lock", async function () {
-        let bridge = await Wormhole.deployed();
-        // Expect user to have a balance
-        let wa = new WrappedAsset("0xC3697aaf5B3D354214548248710414812099bc93")
+        assert.equal(result.vm.version, 1);
+        assert.equal(result.vm.timestamp, timestamp);
+        assert.equal(result.vm.nonce, nonce);
+        assert.equal(result.vm.emitterChainId, emitterChainId);
+        assert.equal(result.vm.emitterAddress, emitterAddress);
+        assert.equal(result.vm.payload, data);
+        assert.equal(result.vm.guardianSetIndex, 0);
+
+        assert.equal(result.valid, true);
+
+        assert.equal(result.reason, "");
+    })
+
+    it("should set and enforce fees", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+        const accounts = await web3.eth.getAccounts();
+
+        const timestamp = 1000;
+        const nonce = 1001;
+        const emitterChainId = testGovernanceChainId;
+        const emitterAddress = testGovernanceContract
+        let data = "0x00000000000000000000000000000000000000000000000000000000436f726503";
+
+        data += [
+            web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
+            web3.eth.abi.encodeParameter("uint256", 1111).substring(2),
+            web3.eth.abi.encodeParameter("uint256", 2222).substring(2),
+        ].join('')
+
+        const vm = await signAndEncodeVM(
+            timestamp,
+            nonce,
+            emitterChainId,
+            emitterAddress,
+            data,
+            [
+                testSigner1PK,
+                testSigner2PK
+            ],
+            0
+        );
+
+
+        let before = await initialized.methods.messageFee().call();
+
+        let set = await initialized.methods.submitSetMessageFee("0x" + vm).send({
+            value : 0,
+            from : accounts[0],
+            gasLimit : 1000000
+        });
 
 
-        await bridge.lockAssets(wa.address, "500000000000000000", "0x0", 4, 2, false);
-        let balance = await wa.balanceOf("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1");
+        let after = await initialized.methods.messageFee().call();
+
+        assert.notEqual(before, after);
+        assert.equal(after, 1111);
+
+        // test non-persisted message
+        await initialized.methods.publishMessage(
+            "0x123",
+            "0x123321",
+            false
+        ).send({
+            from : accounts[0],
+            value : 1111
+        })
+        // test persisted message
+        await initialized.methods.publishMessage(
+            "0x123",
+            "0x123321",
+            true
+        ).send({
+            from : accounts[0],
+            value : 2222
+        })
+
+        let failed = false;
+        try {
+            await initialized.methods.publishMessage(
+                "0x123",
+                "0x123321",
+                false
+            ).send({
+                value : 1110,
+                from : accounts[0]
+            })
+        } catch(e) {
+            failed = true
+        }
 
 
-        // Expect user balance to decrease
-        assert.equal(balance, "500000000000000000");
+        assert.equal(failed, true);
+    })
+
+    it("should transfer out collected fees", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+        const accounts = await web3.eth.getAccounts();
+
+        const receiver = "0x" + zeroPadBytes( Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16), 20);
+
+        const timestamp = 1000;
+        const nonce = 1001;
+        const emitterChainId = testGovernanceChainId;
+        const emitterAddress = testGovernanceContract
+        let data = "0x00000000000000000000000000000000000000000000000000000000436f726504";
+
+        data += [
+            web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
+            web3.eth.abi.encodeParameter("uint256", 11).substring(2),
+            web3.eth.abi.encodeParameter("address", receiver).substring(2),
+        ].join('')
+
+        const vm = await signAndEncodeVM(
+            timestamp,
+            nonce,
+            emitterChainId,
+            emitterAddress,
+            data,
+            [
+                testSigner1PK,
+                testSigner2PK
+            ],
+            0
+        );
+
+        let WHBefore = await web3.eth.getBalance(Wormhole.address);
+        let receiverBefore = await web3.eth.getBalance(receiver);
+
+        let set = await initialized.methods.submitTransferFees("0x" + vm).send({
+            value : 0,
+            from : accounts[0],
+            gasLimit : 1000000
+        });
 
 
-        // Expect contract balance to be 0 since tokens have been burned
-        balance = await wa.balanceOf(bridge.address);
-        assert.equal(balance, "0");
-    });
+        let WHAfter = await web3.eth.getBalance(Wormhole.address);
+        let receiverAfter = await web3.eth.getBalance(receiver);
+
+        assert.equal(WHBefore - WHAfter, 11);
+        assert.equal(receiverAfter - receiverBefore, 11);
+    })
+
+    it("should accept a new guardian set", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+        const accounts = await web3.eth.getAccounts();
+
+        const timestamp = 1000;
+        const nonce = 1001;
+        const emitterChainId = testGovernanceChainId;
+        const emitterAddress = testGovernanceContract
+        let data = "0x00000000000000000000000000000000000000000000000000000000436f726502";
+
+        let oldIndex = Number(await initialized.methods.getCurrentGuardianSetIndex().call());
+
+        data += [
+            web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
+            web3.eth.abi.encodeParameter("uint32", oldIndex + 1).substring(2 + (64 - 8)),
+            web3.eth.abi.encodeParameter("uint8", 3).substring(2 + (64 - 2)),
+            web3.eth.abi.encodeParameter("address", testSigner1.address).substring(2 + (64 - 40)),
+            web3.eth.abi.encodeParameter("address", testSigner2.address).substring(2 + (64 - 40)),
+            web3.eth.abi.encodeParameter("address", testSigner3.address).substring(2 + (64 - 40)),
+        ].join('')
+
+        const vm = await signAndEncodeVM(
+            timestamp,
+            nonce,
+            emitterChainId,
+            emitterAddress,
+            data,
+            [
+                testSigner1PK,
+                testSigner2PK
+            ],
+            0
+        );
+
+        let set = await initialized.methods.submitNewGuardianSet("0x" + vm).send({
+            value : 0,
+            from : accounts[0],
+            gasLimit : 1000000
+        });
 
 
-    it("should transfer tokens in and out", async function () {
-        let bridge = await Wormhole.deployed();
-        let token = await ERC20.new("Test Token", "TKN");
+        let index = await initialized.methods.getCurrentGuardianSetIndex().call();
 
 
-        await token.mint("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1", "1000000000000000000000000000");
-        // Expect user to have a balance
-        assert.equal(await token.balanceOf("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"), "1000000000000000000000000000");
+        assert.equal(oldIndex + 1, index);
 
 
-        // Approve bridge
-        await token.approve(bridge.address, "1000000000000000000000000000");
+        assert.equal(index, 1);
 
 
-        // Transfer of that token out of the contract should not work
-        let threw = false;
-        try {
-            await bridge.submitVAA("0x01000000000100078f0fe9406808b1e5003867ab74aa2085153b7735b329640d275ea943dd115d00e356c6d343142d9190872c11d2de898d075cea7f4e85ff2188af299e26a14200000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c102000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb080000000000000000000000000000000000000000000000000de0b6b3a7640000");
-        } catch (e) {
-            threw = true;
-        }
-        assert.isTrue(threw);
-
-        // Lock assets
-        let ev = await bridge.lockAssets(token.address, "1000000000000000000000000000", "0x1230000000000000000000000000000000000000000000000000000000000000", 3, 3, false);
-
-        // Check that the lock event was emitted correctly
-        assert.lengthOf(ev.logs, 1)
-        assert.equal(ev.logs[0].event, "LogTokensLocked")
-        assert.equal(ev.logs[0].args.target_chain, "3")
-        assert.equal(ev.logs[0].args.token_chain, "2")
-        assert.equal(ev.logs[0].args.token, "0x000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb")
-        assert.equal(ev.logs[0].args.sender, "0x00000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1")
-        assert.equal(ev.logs[0].args.recipient, "0x1230000000000000000000000000000000000000000000000000000000000000")
-        assert.equal(ev.logs[0].args.amount, "1000000000000000000")
-
-        // Check that the tokens were transferred to the bridge
-        assert.equal(await token.balanceOf("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"), "0");
-        assert.equal(await token.balanceOf(bridge.address), "1000000000000000000000000000");
-
-        // Transfer this token back - This also checks decimal realignment
-        await bridge.submitVAA("0x01000000000100078f0fe9406808b1e5003867ab74aa2085153b7735b329640d275ea943dd115d00e356c6d343142d9190872c11d2de898d075cea7f4e85ff2188af299e26a14200000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c102000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb080000000000000000000000000000000000000000000000000de0b6b3a7640000");
-        assert.equal(await token.balanceOf("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"), "1000000000000000000000000000");
-        assert.equal(await token.balanceOf(bridge.address), "0");
-    });
+        let guardians = await initialized.methods.getGuardianSet(index).call();
 
 
-    it("should accept validator set change", async function () {
-        let bridge = await Wormhole.deployed();
+        assert.equal(guardians.expirationTime, 0);
 
 
-        // Push time by 1000
-        await advanceTimeAndBlock(1000);
-        let ev = await bridge.submitVAA("0x01000000000100a33c022217ccb87a5bc83b71e6377fff6639e7904d9e9995a42dc0867dc2b0bc5d1aacc3752ea71cf4d85278526b5dd40b0343667a2d4434a44cbf7844181a1000000007d0010000000101e06a9adfeb38a8ee4d00e89307c016d0749679bd")
-        assert.lengthOf(ev.logs, 1)
-        assert.equal(ev.logs[0].event, "LogGuardianSetChanged")
+        assert.lengthOf(guardians[0], 3);
+        assert.equal(guardians[0][0], testSigner1.address);
+        assert.equal(guardians[0][1], testSigner2.address);
+        assert.equal(guardians[0][2], testSigner3.address);
 
 
-        // Expect guardian set to transition to 1
-        assert.equal(await bridge.guardian_set_index(), 1);
-    });
+        let oldGuardians = await initialized.methods.getGuardianSet(oldIndex).call();
 
 
-    it("should not accept guardian set change from old guardians", async function () {
-        let bridge = await Wormhole.deployed();
+        const time = (await web3.eth.getBlock("latest")).timestamp;
 
 
-        // Test update guardian set VAA from guardian set 0; timestamp 2000
-        let threw = false;
-        try {
-            await bridge.submitVAA("0x01000000000100d90d6f9cbc0458599cbe4d267bc9221b54955b94cb5cb338aeb845bdc9dd275f558871ea479de9cc0b44cfb2a07344431a3adbd2f98aa86f4e12ff4aba061b7f00000007d00100000001018575df9b3c97b4e267deb92d93137844a97a0132")
-        } catch (e) {
-            threw = true;
-            assert.equal(e.reason, "only the current guardian set can change the guardian set")
-        }
-        assert.isTrue(threw, "old guardian set could make changes")
-    });
+        // old guardian set expiry is set
+        assert.ok(
+            oldGuardians.expirationTime > Number(time) + 86000
+            && oldGuardians.expirationTime < Number(time) + 88000
+        );
+    })
 
 
-    it("should time out guardians", async function () {
-        let bridge = await Wormhole.deployed();
+    it("should accept smart contract upgrades", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+        const accounts = await web3.eth.getAccounts();
 
 
-        // Test VAA from guardian set 0; timestamp 1000
-        await bridge.submitVAA("0x0100000000010034890d1c1aa2455d083602996d924ca9ba2fd9641dcdaa3b0811c9ed37e831a8433b40b0f0779fa16be2daaf53ede378530a135b68ac95814c9d25023a29580e01000003e810000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
+        const mock = await MockImplementation.new();
 
 
-        await advanceTimeAndBlock(1000);
+        const timestamp = 1000;
+        const nonce = 1001;
+        const emitterChainId = testGovernanceChainId;
+        const emitterAddress = testGovernanceContract
+        let data = "0x00000000000000000000000000000000000000000000000000000000436f726501";
 
 
-        // Test VAA from guardian set 0; timestamp 2000 - should not work anymore
-        let threw = false;
-        try {
-            await bridge.submitVAA("0x010000000001005a55b73ff79bc3cc39bec075ae28ae8351eee1428a7701f0d47fec5736bcfd9e158b49e6282678c425aed8185233ea4ef033af33bd450a77a46ddbadf3ea09ba00000007d010000000380102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
-        } catch (e) {
-            threw = true;
-            assert.equal(e.reason, "guardian set has expired")
-        }
-        assert.isTrue(threw, "guardian set did not expire")
-
-        // Test same transaction with guardian set 1; timestamp 2000
-        await bridge.submitVAA("0x01000000010100958a39752b14ab62a3dcdb37a8642c4ca1085c6ac77205a462ee5bb3650c92407675729615f69255fc150835621e96c917e68929efb975db9647636543c710f200000007d010000000380102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
-    });
+        data += [
+            web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
+            web3.eth.abi.encodeParameter("address", mock.address).substring(2),
+        ].join('')
 
 
-    it("mismatching guardian set and signature should not work", async function () {
-        let bridge = await Wormhole.deployed();
+        const vm = await signAndEncodeVM(
+            timestamp,
+            nonce,
+            emitterChainId,
+            emitterAddress,
+            data,
+            [
+                testSigner1PK,
+                testSigner2PK,
+                testSigner3PK
+            ],
+            1
+        );
 
 
-        // Test VAA signed by guardian set 0 but set guardian set index to 1
-        let threw = false;
-        try {
-            await bridge.submitVAA("0x01000000010100724a1d2cda45da3cf38f9e0eaef01742210f4deabf9b9d4b20127f6a200a94805928e26ae5f5ab8c3e1cb6d5231d4c48aacae0841513fbd3d9d430be7145db8200000007d010000000390102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
-        } catch (e) {
-            threw = true;
-            assert.equal(e.reason, "VAA signature invalid")
-        }
-        assert.isTrue(threw, "invalid signature accepted")
-    });
+        let before = await web3.eth.getStorageAt(Wormhole.address, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc");
 
 
-    it("quorum should be honored", async function () {
-        let bridge = await Wormhole.deployed();
+        assert.equal(before.toLowerCase(), Implementation.address.toLowerCase());
 
 
-        // Update to validator set 2 with 6 signers
-        await bridge.submitVAA("0x010000000101007a8681fbb4eb93fe71d2608bacdd6ac8d7f07987d531435fc4e0e9224fcf5d087991860eb61b73671db864e7b33894ec82f7ffb17ba5a888712fb6be11df4b030100000fa0010000000206befa429d57cd18b7f8a4d91a2da9ab4af05d0fbee06a9adfeb38a8ee4d00e89307c016d0749679bd8575df9b3c97b4e267deb92d93137844a97a01320427cda59902dc6eb0c1bd2b6d38f87c5552b348bfea822f75c42e1764c791b8fe04a7b10ddb38572f5fe0b158147e7260f14062556afc94eece55ff")
+        let set = await initialized.methods.submitContractUpgrade("0x" + vm).send({
+            value : 0,
+            from : accounts[0],
+            gasLimit: 1000000
+        });
 
 
-        // Test VAA signed by only 3 signers
-        let threw = false;
-        try {
-            await bridge.submitVAA("0x01000000020300e94bec8a17bd313522cdfea30cec5406a41a4cc4b6ec416a633ebe3aca070ae448e370e0a2e7c67fed04a2b825f56cf226c76e6ecd2e71865642393bf729dad80101ccf89506bef58d8cb12baabd60e3304cfb90ef0ef0657caba9c37ffa0d34a54c3aacd1a475ef4c72f24e8d9ce1e2de51e580ce85b18356436b6cda9e2ae9abc0010285f0d3c0d1cd421ce0ae515db1ac3b623c17d4702564971932fb9925c0506fc76e43a7283c712ee680cf058c3c447653c352ca9827b1780e1fc88a61540092d90100000fa010000000390102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
-        } catch (e) {
-            threw = true;
-            assert.equal(e.reason, "no quorum")
-        }
-        assert.isTrue(threw, "accepted only 3 signatures")
+        let after = await web3.eth.getStorageAt(Wormhole.address, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc");
 
 
-        // Test VAA signed by 5 signers (all except i=3)
-        await bridge.submitVAA("0x01000000020500e94bec8a17bd313522cdfea30cec5406a41a4cc4b6ec416a633ebe3aca070ae448e370e0a2e7c67fed04a2b825f56cf226c76e6ecd2e71865642393bf729dad80101ccf89506bef58d8cb12baabd60e3304cfb90ef0ef0657caba9c37ffa0d34a54c3aacd1a475ef4c72f24e8d9ce1e2de51e580ce85b18356436b6cda9e2ae9abc001033e9b4ff5fb545e964e907349e3dab0057c408c832bb31fb76fae7f81c3e488ea4897ce14db61c46d1169bd64b449498b1a18dee4de0ef2038b1c7e3a4a0239a0010432eac9532a4c0ce279d6a3018a5ea0d74402eb6969df5d444f20e0cca66d3b4c53e41cb18648f64af100c7410692e83fa16e5696b1f5f0d517653b003e22689800055859330bd1fee76d99728803fa26d739e494e1a232f5658150c2a2c97e1c9722793bdd83bd7cbb4a39b587b45093ee76187c72dfd68d64b7c0abc32bfef5d55c0000000fa010000000390102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
-    });
+        assert.equal(after.toLowerCase(), mock.address.toLowerCase());
 
 
-    it("should correctly adjust decimals", async function () {
-        let bridge = await Wormhole.deployed();
-        let token = await ERC20.new("Test Token", "TKN");
+        const mockImpl = new web3.eth.Contract(MockImplementation.abi, Wormhole.address);
 
 
-        await token.mint("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1", "25000000000000000000000000000000000000");
+        let isUpgraded = await mockImpl.methods.testNewImplementationActive().call();
 
 
-        // Approve bridge
-        await token.approve(bridge.address, "25000000000000000000000000000000000000");
+        assert.ok(isUpgraded);
+    })
 
 
-        // Lock assets
-        let ev = await bridge.lockAssets(token.address, "2000000000000000003", "0x1230000000000000000000000000000000000000000000000000000000000000", 3, 3, false);
+    it("should revert governance packets from old guardian set", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+        const accounts = await web3.eth.getAccounts();
 
 
-        // Check that the correct amount was logged
-        assert.lengthOf(ev.logs, 1)
-        assert.equal(ev.logs[0].event, "LogTokensLocked")
-        assert.equal(ev.logs[0].args.amount, "2000000000")
-        assert.equal(ev.logs[0].args.token_decimals, "9")
+        let data = "0x00000000000000000000000000000000000000000000000000000000436f726504";
+        data += [
+            web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
+            web3.eth.abi.encodeParameter("uint256", 1).substring(2),
+            web3.eth.abi.encodeParameter("address", "0x0000000000000000000000000000000000000000").substring(2),
+        ].join('')
 
 
-        ev = await bridge.lockAssets(token.address, "2000000000000000000", "0x1230000000000000000000000000000000000000000000000000000000000000", 3, 3, false);
-        // Check that the correct amount was logged
-        assert.lengthOf(ev.logs, 1)
-        assert.equal(ev.logs[0].event, "LogTokensLocked")
-        assert.equal(ev.logs[0].args.amount, "2000000000")
+        const vm = await signAndEncodeVM(
+            0,
+            0,
+            testGovernanceChainId,
+            testGovernanceContract,
+            data,
+            [
+                testSigner1PK,
+                testSigner2PK
+            ],
+            0
+        );
 
 
-        await bridge.lockAssets(token.address, "18446744069709551615000000000", "0x1230000000000000000000000000000000000000000000000000000000000000", 3, 3, false);
-        let threw = false;
+        let failed = false;
         try {
         try {
-            await bridge.lockAssets(token.address, "1000000000", "0x1230000000000000000000000000000000000000000000000000000000000000", 3, 3, false);
-        } catch (e) {
-            threw = true;
-            assert.equal(e.reason, "bridge balance would exceed maximum")
+            await initialized.methods.submitTransferFees("0x" + vm).send({
+                value : 0,
+                from : accounts[0],
+                gasLimit : 1000000
+            });
+            asset.fail("governance packet of old guardian set accepted")
+        } catch(e) {
+            assert.equal(e.data[Object.keys(e.data)[0]].reason, "not signed by current guardian set")
         }
         }
-        assert.isTrue(threw, "accepted total bridge balance > MAX_U64")
-    });
-
-    it("should refund dust", async function () {
-        let bridge = await Wormhole.deployed();
-        let token = await ERC20.new("Test Token", "TKN");
+    })
+
+    it("should time out old gardians", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+
+        const timestamp = 1000;
+        const nonce = 1001;
+        const emitterChainId = 11;
+        const emitterAddress = "0x0000000000000000000000000000000000000000000000000000000000000eee"
+        const data = "0xaaaaaa";
+
+        const vm = await signAndEncodeVM(
+            timestamp,
+            nonce,
+            emitterChainId,
+            emitterAddress,
+            data,
+            [
+                testSigner1PK,
+                testSigner2PK
+            ],
+            0
+        );
+
+        // this should pass
+        const current = await initialized.methods.parseAndVerifyVM("0x" + vm).call();
+
+        assert.equal(current.valid, true)
+
+        await advanceTimeAndBlock(100000);
+
+        const expired = await initialized.methods.parseAndVerifyVM("0x" + vm).call();
+
+        assert.equal(expired.valid, false)
+    })
+
+    it("should revert governance packets from wrong governance chain", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+        const accounts = await web3.eth.getAccounts();
+
+        let data = "0x00000000000000000000000000000000000000000000000000000000436f726504";
+        data += [
+            web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
+            web3.eth.abi.encodeParameter("uint256", 1).substring(2),
+            web3.eth.abi.encodeParameter("address", "0x0000000000000000000000000000000000000000").substring(2),
+        ].join('')
+
+        const vm = await signAndEncodeVM(
+            0,
+            0,
+            999,
+            testGovernanceContract,
+            data,
+            [
+                testSigner1PK,
+                testSigner2PK,
+                testSigner3PK,
+            ],
+            1
+        );
 
 
-        await token.mint("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1", "5000000100");
-
-        // Approve bridge
-        await token.approve(bridge.address, "5000000100");
+        try {
+            await initialized.methods.submitTransferFees("0x" + vm).send({
+                value : 0,
+                from : accounts[0],
+                gasLimit : 1000000
+            });
+            asset.fail("governance packet from wrong governance chain accepted")
+        } catch(e) {
+            assert.equal(e.data[Object.keys(e.data)[0]].reason, "wrong governance chain")
+        }
+    })
+
+    it("should revert governance packets from wrong governance contract", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+        const accounts = await web3.eth.getAccounts();
+
+        let data = "0x00000000000000000000000000000000000000000000000000000000436f726504";
+        data += [
+            web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
+            web3.eth.abi.encodeParameter("uint256", 1).substring(2),
+            web3.eth.abi.encodeParameter("address", "0x0000000000000000000000000000000000000000").substring(2),
+        ].join('')
+
+        const vm = await signAndEncodeVM(
+            0,
+            0,
+            testGovernanceChainId,
+            "0x00000000000000000000000000000000000000000000000000000000436f7265",
+            data,
+            [
+                testSigner1PK,
+                testSigner2PK,
+                testSigner3PK,
+            ],
+            1
+        );
 
 
-        // Lock assets
-        let ev = await bridge.lockAssets(token.address, "1000000005", "0x1230000000000000000000000000000000000000000000000000000000000000", 3, 3, false);
+        try {
+            await initialized.methods.submitTransferFees("0x" + vm).send({
+                value : 0,
+                from : accounts[0],
+                gasLimit : 1000000
+            });
+            asset.fail("governance packet from wrong governance contract accepted")
+        } catch(e) {
+            assert.equal(e.data[Object.keys(e.data)[0]].reason, "wrong governance contract")
+        }
+    })
+
+    it("should revert on governance packets that already have been applied", async function(){
+        const initialized = new web3.eth.Contract(ImplementationFullABI, Wormhole.address);
+        const accounts = await web3.eth.getAccounts();
+
+        let data = "0x00000000000000000000000000000000000000000000000000000000436f726504";
+        data += [
+            web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)),
+            web3.eth.abi.encodeParameter("uint256", 1).substring(2),
+            web3.eth.abi.encodeParameter("address", "0x0000000000000000000000000000000000000000").substring(2),
+        ].join('')
+
+        const vm = await signAndEncodeVM(
+            0,
+            0,
+            testGovernanceChainId,
+            testGovernanceContract,
+            data,
+            [
+                testSigner1PK,
+                testSigner2PK,
+                testSigner3PK,
+            ],
+            1
+        );
+
+        await initialized.methods.submitTransferFees("0x" + vm).send({
+            value : 0,
+            from : accounts[0],
+            gasLimit : 1000000
+        });
 
 
-        // Check that dust was not subtracted
-        assert.lengthOf(ev.logs, 1)
-        assert.equal(ev.logs[0].event, "LogTokensLocked")
-        assert.equal(ev.logs[0].args.amount, "1")
-        assert.equal(await token.balanceOf("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"), "4000000095");
+        try {
+            await initialized.methods.submitTransferFees("0x" + vm).send({
+                value : 0,
+                from : accounts[0],
+                gasLimit : 1000000
+            });
+
+            asset.fail("governance packet accepted twice")
+        } catch(e) {
+            assert.equal(e.data[Object.keys(e.data)[0]].reason, "governance action already consumed")
+        }
+    })
+});
 
 
-        // Lock assets
-        ev = await bridge.lockAssets(token.address, "1000000005", "0x1230000000000000000000000000000000000000000000000000000000000000", 3, 3, true);
+const signAndEncodeVM = async function(
+    timestamp,
+    nonce,
+    emitterChainId,
+    emitterAddress,
+    data,
+
+    signers,
+    guardianSetIndex
+){
+    const body = [
+        web3.eth.abi.encodeParameter("uint32", timestamp).substring(2 + (64 - 8)),
+        web3.eth.abi.encodeParameter("uint32", nonce).substring(2 + (64 - 8)),
+        web3.eth.abi.encodeParameter("uint16", emitterChainId).substring(2 + (64 - 4)),
+        web3.eth.abi.encodeParameter("bytes32", emitterAddress).substring(2),
+        web3.eth.abi.encodeParameter("uint8", data.length / 2 - 1).substring(2 + (64 - 2)),
+        data.substr(2)
+    ]
+
+    const hash = web3.utils.soliditySha3("0x"+body.join(""))
+
+    let signatures = "";
+
+    for(let i in signers){
+        const ec = new elliptic.ec("secp256k1");
+        const key = ec.keyFromPrivate(signers[i]);
+        const signature = key.sign(hash.substr(2), { canonical: true });
+
+        const packSig = [
+            zeroPadBytes(signature.r.toString(16), 32),
+            zeroPadBytes(signature.s.toString(16), 32),
+            web3.eth.abi.encodeParameter("uint8", signature.recoveryParam).substr(2 + (64 - 2)),
+            web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2))
+        ]
+
+        signatures += packSig.join("")
+    }
+
+    const vm = [
+        web3.eth.abi.encodeParameter("uint8", 1).substring(2 + (64 - 2)),
+        web3.eth.abi.encodeParameter("uint32", guardianSetIndex).substring(2 + (64 - 8)),
+        web3.eth.abi.encodeParameter("uint8", signers.length).substring(2 + (64 - 2)),
+
+        signatures,
+        body.join("")
+    ].join("");
+
+    return vm
+}
 
 
-        // Check that dust was refunded
-        assert.lengthOf(ev.logs, 1)
-        assert.equal(ev.logs[0].event, "LogTokensLocked")
-        assert.equal(ev.logs[0].args.amount, "1")
-        assert.equal((await token.balanceOf("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1")).toString(), "3000000095");
-    });
-});
+function zeroPadBytes(value, length) {
+    while (value.length < 2 * length) {
+        value = "0" + value;
+    }
+    return value;
+}

+ 9 - 10
ethereum/truffle-config.js

@@ -42,7 +42,7 @@ module.exports = {
         //
         //
         development: {
         development: {
             host: "127.0.0.1",     // Localhost (default: none)
             host: "127.0.0.1",     // Localhost (default: none)
-            port: 8545,            // Standard Ethereum port (default: none)
+            port: 7545,            // Standard Ethereum port (default: none)
             network_id: "*",       // Any network (default: none)
             network_id: "*",       // Any network (default: none)
         },
         },
         // Another network with more advanced options...
         // Another network with more advanced options...
@@ -80,16 +80,15 @@ module.exports = {
     // Configure your compilers
     // Configure your compilers
     compilers: {
     compilers: {
         solc: {
         solc: {
-            version: "0.6.12",    // Fetch exact version from solc-bin (default: truffle's version)
+            version: "0.8.4",    // Fetch exact version from solc-bin (default: truffle's version)
             // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
             // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
-            // settings: {          // See the solidity docs for advice about optimization and evmVersion
-            //  optimizer: {
-            //    enabled: false,
-            //    runs: 200
-            //  },
-            //  evmVersion: "byzantium"
-            // }
-        },
+            settings: {          // See the solidity docs for advice about optimization and evmVersion
+                 optimizer: {
+                   enabled: true,
+                   runs: 200
+                 },
+            }
+        }
     },
     },
 
 
     plugins: [
     plugins: [

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.