Add unit test and fuzzing against Vyper (#20)
This commit is contained in:
parent
bc9b7ccfd3
commit
6c07707f1d
|
@ -0,0 +1,3 @@
|
|||
[submodule "lib/ds-test"]
|
||||
path = lib/ds-test
|
||||
url = https://github.com/dapphub/ds-test
|
15
Makefile
15
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
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
30
circle.yml
30
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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit eb7148d43c1ca6f9890361e2e2378364af2430ba
|
|
@ -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 ];
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue