diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..e12471968 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/ds-test"] + path = lib/ds-test + url = https://github.com/dapphub/ds-test diff --git a/Makefile b/Makefile index 7de2d6d6b..3615a47cd 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,24 @@ all: compile clean: @rm -f DepositContract.abi DepositContract.bin IDepositContract.abi IDepositContract.bin deposit_contract.json + @rm -f DepositContractTest.abi DepositContractTest.bin + @rm -f VyperSetup.abi VyperSetup.bin + @rm -f DSTest.abi DSTest.bin + @rm -rf combined.json compile: clean - @solc --metadata-literal --bin --abi --overwrite -o . deposit_contract.sol + @git submodule update --recursive --init + @solc --metadata-literal --bin --abi --combined-json=abi,bin,bin-runtime,srcmap,srcmap-runtime,ast,metadata,storage-layout --overwrite -o . deposit_contract.sol tests/deposit_contract.t.sol @echo -n '{"abi": ' > deposit_contract.json @cat DepositContract.abi >> deposit_contract.json @echo -n ', "bytecode": "0x' >> deposit_contract.json @cat DepositContract.bin >> deposit_contract.json @echo -n '"}' >> deposit_contract.json + + +export DAPP_SKIP_BUILD:=1 +export DAPP_SRC:=. +export DAPP_JSON:=combined.json + +test: + dapp test -v --fuzz-runs 5 diff --git a/README.md b/README.md index f34864759..f596b5a11 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,11 @@ The motivation is to run the SMTChecker and the new Yul IR generator option (`-- 5. Finally in the `eth2.0-specs` directory run `make test_deposit_contract` to execute the tests The Makefile currently compiles the code without optimisations. To enable optimisations add `--optimize` to the `solc` line. + + +## Running randomized `dapp` tests: + +Install the latest version of `dapp` by following the instructions at [dapp.tools](https://dapp.tools/). Then run +```sh +make test +``` diff --git a/circle.yml b/circle.yml index d4b631593..9f72b396c 100644 --- a/circle.yml +++ b/circle.yml @@ -1,4 +1,5 @@ version: 2.0 + jobs: build: docker: @@ -23,3 +24,32 @@ jobs: cd eth2.0-specs make install_deposit_contract_tester make test_deposit_contract + - persist_to_workspace: + root: . + paths: + - combined.json + - lib + + test: + docker: + - image: nixorg/nix:circleci + steps: + - checkout + - attach_workspace: + at: /tmp/ + - run: + name: Test the contract + command: | + cp /tmp/combined.json . + cp -r /tmp/lib/* lib + nix-shell --command 'make test' + +workflows: + version: 2 + + build_and_test: + jobs: + - build + - test: + requires: + - build diff --git a/lib/ds-test b/lib/ds-test new file mode 160000 index 000000000..eb7148d43 --- /dev/null +++ b/lib/ds-test @@ -0,0 +1 @@ +Subproject commit eb7148d43c1ca6f9890361e2e2378364af2430ba diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..d0727d49a --- /dev/null +++ b/shell.nix @@ -0,0 +1,18 @@ +let dapptools = builtins.fetchGit { + url = "https://github.com/dapphub/dapptools.git"; + rev = "11dcefe1f03b0acafe76b4d7d54821ef6bd63131"; + }; + nixpkgs = builtins.fetchGit { + url = "https://github.com/nixos/nixpkgs"; + ref = "release-19.03"; + rev = "f1707d8875276cfa110139435a7e8998b4c2a4fd"; + }; + pkgs-for-dapp = import nixpkgs { + overlays = [ + (import (dapptools + /overlay.nix)) + ]; + }; +in +pkgs-for-dapp.mkShell { + buildInputs = [ pkgs-for-dapp.dapp pkgs-for-dapp.solc pkgs-for-dapp.hevm ]; +} diff --git a/tests/deposit_contract.t.sol b/tests/deposit_contract.t.sol new file mode 100644 index 000000000..e3a5124b6 --- /dev/null +++ b/tests/deposit_contract.t.sol @@ -0,0 +1,142 @@ +pragma solidity ^0.6.0; + +import "../lib/ds-test/src/test.sol"; + +import "./vyper_setup.sol"; +import "../deposit_contract.sol"; + +contract DepositContractTest is DSTest { + DepositContract depositContract_sol; + DepositContract depositContract_vyp; + uint64 constant GWEI = 1000000000; + + function setUp() public { + VyperSetup vyperSetup = new VyperSetup(); + depositContract_vyp = DepositContract(vyperSetup.deployDeposit()); + depositContract_sol = new DepositContract(); + } + + // --- SUCCESS TESTS --- + + // Tests initilized storage values, comparing vyper and solidity + function test_empty_root() public { + bytes32 zHash = 0x0000000000000000000000000000000000000000000000000000000000000000; + bytes32 zHashN = zHash; + for (uint i = 0; i <= 31; i++) { + zHashN = sha256(abi.encodePacked(zHashN, zHashN)); + } + assertEq(sha256(abi.encodePacked(zHashN, zHash)), depositContract_vyp.get_deposit_root()); + assertEq(depositContract_sol.get_deposit_root(), depositContract_vyp.get_deposit_root()); + } + + // Generates 16 random deposits, insert them in both vyper and solidity version and compare get_deposit_root after each insertion + function test_16_deposits(bytes32[16] memory pubkey_one, bytes16[16] memory pubkey_two, bytes32[16] memory _withdrawal_credentials, + bytes32[16] memory sig_one, bytes32[16] memory sig_two, bytes32[16] memory sig_three, uint32[16] memory amount) public { + uint j; + for (uint i = 0; i < 16; i++) { + // as of dcaa774, the solidity version is more restrictive than vyper and requires deposits to be divisible by GWEI + uint64 gweiamount = amount[i] * GWEI; + if (1 ether <= gweiamount) { + j++; + deposit_in(depositContract_sol, pubkey_one[i], pubkey_two[i], _withdrawal_credentials[i], sig_one[i], sig_two[i], sig_three[i], gweiamount); + deposit_in(depositContract_vyp, pubkey_one[i], pubkey_two[i], _withdrawal_credentials[i], sig_one[i], sig_two[i], sig_three[i], gweiamount); + + assertEq(depositContract_sol.get_deposit_root(), depositContract_vyp.get_deposit_root()); + assertEq(keccak256(abi.encodePacked(depositContract_sol.get_deposit_count())), keccak256(abi.encodePacked(depositContract_vyp.get_deposit_count()))); + assertEq(keccak256(abi.encodePacked(depositContract_sol.get_deposit_count())), keccak256(to_little_endian_64(uint64(j)))); + } + } + } + + // The solidity contract fails when given a deposit which is not divisible by GWEI + + function testFail_deposit_not_divisible_by_gwei(bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials, + bytes32 sig_one, bytes32 sig_two, bytes32 sig_three) public { + deposit_in(depositContract_sol, pubkey_one, pubkey_two, _withdrawal_credentials, sig_one, sig_two, sig_three, 1 ether + 1); + } + + // --- FAILURE TESTS --- + + // if the node is given randomly instead of as the ssz root, the chances of success are so unlikely that we can assert it to be false + function testFail_malformed_node_vyp(bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials, bytes32 sig_one, + bytes32 sig_two, bytes32 sig_three, uint64 amount, bytes32 node) public { + bytes memory pubkey = abi.encodePacked(pubkey_one, pubkey_two); + bytes memory withdrawal_credentials = abi.encodePacked(_withdrawal_credentials); //I wish just recasting to `bytes` would work.. + bytes memory signature = abi.encodePacked(sig_one, sig_two, sig_three); + depositContract_vyp.deposit.value(amount)(pubkey, withdrawal_credentials, signature, node); + } + + // If the node is taken randomly instead of as the ssz root, the chances of success are so unlikely that we can assert it to be false + function testFail_malformed_node_sol(bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials, bytes32 sig_one, + bytes32 sig_two, bytes32 sig_three, uint64 amount, bytes32 node) public { + bytes memory pubkey = abi.encodePacked(pubkey_one, pubkey_two); + bytes memory withdrawal_credentials = abi.encodePacked(_withdrawal_credentials); + bytes memory signature = abi.encodePacked(sig_one, sig_two, sig_three); + depositContract_sol.deposit.value(amount)(pubkey, withdrawal_credentials, signature, node); + } + + // if bytes lengths are wrong, the call will fail + function testFail_malformed_calldata_vyp(bytes memory pubkey, bytes memory withdrawal_credentials, bytes memory signature, uint64 amount) public { + if (amount >= 1000000000000000000) { + if (!(pubkey.length == 48 && withdrawal_credentials.length == 32 && signature.length == 96)) { + depositContract_vyp.deposit.value(amount)(pubkey, withdrawal_credentials, signature, + encode_node(pubkey, withdrawal_credentials, signature, to_little_endian_64(amount / GWEI)) + ); + } else { revert(); } + } else { revert(); } + } + + function testFail_malformed_calldata_sol(bytes memory pubkey, bytes memory withdrawal_credentials, bytes memory signature, uint64 amount) public { + if (amount >= 1000000000000000000) { + if (!(pubkey.length == 48 && withdrawal_credentials.length == 32 && signature.length == 96)) { + depositContract_sol.deposit.value(amount)(pubkey, withdrawal_credentials, signature, + encode_node(pubkey, withdrawal_credentials, signature, to_little_endian_64(amount / GWEI)) + ); + } else { revert(); } + } else { revert(); } + } + + // --- HELPER FUNCTIONS --- + + function deposit_in(DepositContract depositContract, bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials, bytes32 sig_one, bytes32 sig_two, bytes32 sig_three, uint64 amount) public { + bytes memory pubkey = abi.encodePacked(pubkey_one, pubkey_two); + bytes memory withdrawal_credentials = abi.encodePacked(_withdrawal_credentials); + bytes memory signature = abi.encodePacked(sig_one, sig_two, sig_three); + bytes32 node = encode_node(pubkey, withdrawal_credentials, signature, to_little_endian_64(amount / GWEI)); + depositContract.deposit.value(amount)(pubkey, withdrawal_credentials, signature, node); + } + + function slice(bytes memory a, uint32 offset, uint32 size) pure internal returns (bytes memory result) { + result = new bytes(size); + for (uint i = 0; i < size; i++) { + result[i] = a[offset+i]; + } + } + + function encode_node(bytes memory pubkey, bytes memory withdrawal_credentials, bytes memory signature, bytes memory amount) public pure returns (bytes32) { + bytes16 zero_bytes16; + bytes24 zero_bytes24; + bytes32 zero_bytes32; + bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, zero_bytes16)); + bytes32 signature_root = sha256(abi.encodePacked( + sha256(abi.encodePacked(slice(signature, 0, 64))), + sha256(abi.encodePacked(slice(signature, 64, 32), zero_bytes32)) + )); + return sha256(abi.encodePacked( + sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)), + sha256(abi.encodePacked(amount, zero_bytes24, signature_root)) + )); + } + + function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) { + ret = new bytes(8); + ret[0] = bytes1(uint8(value & 0xff)); + ret[1] = bytes1(uint8((value >> 8) & 0xff)); + ret[2] = bytes1(uint8((value >> 16) & 0xff)); + ret[3] = bytes1(uint8((value >> 24) & 0xff)); + ret[4] = bytes1(uint8((value >> 32) & 0xff)); + ret[5] = bytes1(uint8((value >> 40) & 0xff)); + ret[6] = bytes1(uint8((value >> 48) & 0xff)); + ret[7] = bytes1(uint8((value >> 56) & 0xff)); + } +} diff --git a/tests/vyper_setup.sol b/tests/vyper_setup.sol new file mode 100644 index 000000000..8d82c754d --- /dev/null +++ b/tests/vyper_setup.sol @@ -0,0 +1,16 @@ +pragma solidity ^0.6.0; + +contract VyperSetup { + // Bytecode from https://github.com/ethereum/eth2.0-specs/blob/dev/deposit_contract/contracts/validator_registration.json + bytes constant public depositCode = hex"740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6101406000601f818352015b600061014051602081106100b757600080fd5b600260c052602060c020015460208261016001015260208101905061014051602081106100e357600080fd5b600260c052602060c020015460208261016001015260208101905080610160526101609050602060c0825160208401600060025af161012157600080fd5b60c0519050606051600161014051018060405190131561014057600080fd5b809190121561014e57600080fd5b6020811061015b57600080fd5b600260c052602060c02001555b81516001018083528114156100a4575b505061123556600436101561000d576110b0565b600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05260001561027f575b6101605261014052600061018052610140516101a0526101c060006008818352015b61018051600860008112156100e8578060000360020a82046100ef565b8060020a82025b905090506101805260ff6101a051166101e052610180516101e0516101805101101561011a57600080fd5b6101e0516101805101610180526101a0517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86000811215610163578060000360020a820461016a565b8060020a82025b905090506101a0525b81516001018083528114156100cb575b5050601860086020820661020001602082840111156101a157600080fd5b60208061022082610180600060045af15050818152809050905090508051602001806102c08284600060045af16101d757600080fd5b50506102c05160206001820306601f82010390506103206102c0516020818352015b826103205110151561020a57610226565b6000610320516102e001535b81516001018083528114156101f9575b50505060206102a05260406102c0510160206001820306601f8201039050610280525b60006102805111151561025b57610277565b602061028051036102a001516020610280510361028052610249565b610160515650005b63c5f2892f600051141561050e57341561029857600080fd5b6000610140526101405161016052600154610180526101a060006020818352015b600160016101805116141561033a5760006101a051602081106102db57600080fd5b600060c052602060c02001546020826102400101526020810190506101605160208261024001015260208101905080610240526102409050602060c0825160208401600060025af161032c57600080fd5b60c0519050610160526103a8565b6000610160516020826101c00101526020810190506101a0516020811061036057600080fd5b600260c052602060c02001546020826101c0010152602081019050806101c0526101c09050602060c0825160208401600060025af161039e57600080fd5b60c0519050610160525b61018060026103b657600080fd5b60028151048152505b81516001018083528114156102b9575b505060006101605160208261046001015260208101905061014051610160516101805163806732896102e0526001546103005261030051600658016100a9565b506103605260006103c0525b6103605160206001820306601f82010390506103c05110151561043d57610456565b6103c05161038001526103c0516020016103c05261041b565b61018052610160526101405261036060088060208461046001018260208501600060045af150508051820191505060006018602082066103e001602082840111156104a057600080fd5b60208061040082610140600060045af150508181528090509050905060188060208461046001018260208501600060045af150508051820191505080610460526104609050602060c0825160208401600060025af16104fe57600080fd5b60c051905060005260206000f350005b63621fd130600051141561061c57341561052757600080fd5b6380673289610140526001546101605261016051600658016100a9565b506101c0526000610220525b6101c05160206001820306601f8201039050610220511015156105725761058b565b610220516101e001526102205160200161022052610550565b6101c08051602001806102808284600060045af16105a857600080fd5b50506102805160206001820306601f82010390506102e0610280516020818352015b826102e0511015156105db576105f7565b60006102e0516102a001535b81516001018083528114156105ca575b5050506020610260526040610280510160206001820306601f8201039050610260f350005b632289511860005114156110af57605060043560040161014037603060043560040135111561064a57600080fd5b60406024356004016101c037602060243560040135111561066a57600080fd5b608060443560040161022037606060443560040135111561068a57600080fd5b63ffffffff6001541061069c57600080fd5b633b9aca006102e0526102e0516106b257600080fd5b6102e05134046102c052633b9aca006102c05110156106d057600080fd5b603061014051146106e057600080fd5b60206101c051146106f057600080fd5b6060610220511461070057600080fd5b610140610360525b6103605151602061036051016103605261036061036051101561072a57610708565b6380673289610380526102c0516103a0526103a051600658016100a9565b50610400526000610460525b6104005160206001820306601f8201039050610460511015156107765761078f565b6104605161042001526104605160200161046052610754565b610340610360525b61036051526020610360510361036052610140610360511015156107ba57610797565b6104008051602001806103008284600060045af16107d757600080fd5b5050610140610480525b61048051516020610480510161048052610480610480511015610803576107e1565b63806732896104a0526001546104c0526104c051600658016100a9565b50610520526000610580525b6105205160206001820306601f82010390506105805110151561084e57610867565b610580516105400152610580516020016105805261082c565b610460610480525b61048051526020610480510361048052610140610480511015156108925761086f565b6105208051602001806105a08284600060045af16108af57600080fd5b505060a061062052610620516106605261014080516020018061062051610660018284600060045af16108e157600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516040818352015b826106005110151561091e5761093f565b600061060051610620516106800101535b815160010180835281141561090d575b505050602061062051610660015160206001820306601f82010390506106205101016106205261062051610680526101c080516020018061062051610660018284600060045af161098f57600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516020818352015b82610600511015156109cc576109ed565b600061060051610620516106800101535b81516001018083528114156109bb575b505050602061062051610660015160206001820306601f820103905061062051010161062052610620516106a05261030080516020018061062051610660018284600060045af1610a3d57600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516020818352015b8261060051101515610a7a57610a9b565b600061060051610620516106800101535b8151600101808352811415610a69575b505050602061062051610660015160206001820306601f820103905061062051010161062052610620516106c05261022080516020018061062051610660018284600060045af1610aeb57600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516060818352015b8261060051101515610b2857610b49565b600061060051610620516106800101535b8151600101808352811415610b17575b505050602061062051610660015160206001820306601f820103905061062051010161062052610620516106e0526105a080516020018061062051610660018284600060045af1610b9957600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516020818352015b8261060051101515610bd657610bf7565b600061060051610620516106800101535b8151600101808352811415610bc5575b505050602061062051610660015160206001820306601f8201039050610620510101610620527f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c561062051610660a160006107005260006101406030806020846107c001018260208501600060045af150508051820191505060006010602082066107400160208284011115610c8c57600080fd5b60208061076082610700600060045af15050818152809050905090506010806020846107c001018260208501600060045af1505080518201915050806107c0526107c09050602060c0825160208401600060025af1610cea57600080fd5b60c0519050610720526000600060406020820661086001610220518284011115610d1357600080fd5b6060806108808260206020880688030161022001600060045af1505081815280905090509050602060c0825160208401600060025af1610d5257600080fd5b60c0519050602082610a600101526020810190506000604060206020820661092001610220518284011115610d8657600080fd5b6060806109408260206020880688030161022001600060045af15050818152809050905090506020806020846109e001018260208501600060045af1505080518201915050610700516020826109e0010152602081019050806109e0526109e09050602060c0825160208401600060025af1610e0157600080fd5b60c0519050602082610a6001015260208101905080610a6052610a609050602060c0825160208401600060025af1610e3857600080fd5b60c0519050610840526000600061072051602082610b000101526020810190506101c0602080602084610b0001018260208501600060045af150508051820191505080610b0052610b009050602060c0825160208401600060025af1610e9d57600080fd5b60c0519050602082610c800101526020810190506000610300600880602084610c0001018260208501600060045af15050805182019150506000601860208206610b800160208284011115610ef157600080fd5b602080610ba082610700600060045af1505081815280905090509050601880602084610c0001018260208501600060045af150508051820191505061084051602082610c0001015260208101905080610c0052610c009050602060c0825160208401600060025af1610f6257600080fd5b60c0519050602082610c8001015260208101905080610c8052610c809050602060c0825160208401600060025af1610f9957600080fd5b60c0519050610ae052606435610ae05114610fb357600080fd5b6001805460018254011015610fc757600080fd5b6001815401815550600154610d0052610d2060006020818352015b60016001610d005116141561101757610ae051610d20516020811061100657600080fd5b600060c052602060c02001556110ab565b6000610d20516020811061102a57600080fd5b600060c052602060c0200154602082610d40010152602081019050610ae051602082610d4001015260208101905080610d4052610d409050602060c0825160208401600060025af161107b57600080fd5b60c0519050610ae052610d00600261109257600080fd5b60028151048152505b8151600101808352811415610fe2575b5050005b5b60006000fd5b61017f6112350361017f60003961017f611235036000f3"; + function write(bytes memory _code) public returns (address target) { + assembly { + target := create(0, add(_code, 0x20), mload(_code)) + } + } + + function deployDeposit() public returns (address) { + return write(depositCode); + } +} +