Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hevm: enchanced control over blockchain context #716

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions nix/build-dapp-package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ in
, deps ? []
, solc ? "${pkgs.solc}/bin/solc"
, shouldFail ? false
, dappFlags ? ""
, doCheck ? true
, dapprc ? ""
, testFlags ? ""
, ... }@args:
pkgs.stdenv.mkDerivation ( rec {
inherit doCheck;
Expand All @@ -48,6 +49,8 @@ in
passthru.libPaths;
buildPhase = ''
mkdir -p out
export LANG=C.UTF-8
ln -s ${pkgs.writeText "dapprc" dapprc} ./.dapprc
export DAPP_SOLC=${solc}
export DAPP_REMAPPINGS="$REMAPPINGS"
export DAPP_SRC=$src
Expand All @@ -59,7 +62,7 @@ in
'';

checkPhase = let
cmd = "DAPP_SKIP_BUILD=1 dapp test ${dappFlags}";
cmd = "DAPP_SKIP_BUILD=1 dapp test ${testFlags}";
in
if shouldFail
then "${cmd} && exit 1 || echo 0"
Expand Down
40 changes: 34 additions & 6 deletions src/dapp-tests/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,10 @@ let
deps = [ ds-test ds-thing ];
};

runTest = { dir, shouldFail, name, dappFlags?"" }: pkgs.buildDappPackage {
inherit name shouldFail;
runTest = { dir, shouldFail, name, dapprc ? "", testFlags ? "" }: pkgs.buildDappPackage {
inherit name shouldFail testFlags dapprc;
solc=solc-0_6_7;
src = dir;
dappFlags = "${dappFlags}";
deps = [ ds-test ds-token ds-math ];
checkInputs = with pkgs; [ hevm jq seth dapp solc ];
};
Expand All @@ -128,15 +127,44 @@ in
dir = ./pass;
name = "dappTestsShouldPass";
shouldFail = false;
dappFlags = "--max-iterations 50 --smttimeout 600000 --ffi";
testFlags = "--max-iterations 50 --smttimeout 600000 --ffi -v";
};

envVars = let
envVarTest = match : dapprc : runTest {
testFlags = "--match ${match}";
dir = ./env;
name = "dappTestEnvVar";
shouldFail = false;
inherit dapprc;
};
seth = "${pkgs.seth}/bin/seth";
in pkgs.recurseIntoAttrs {
# we get "hevm: insufficient balance for gas cost" if we run these together...
# maybe we need an env var to set the balance for the origin?
origin = envVarTest "origin.sol" "export DAPP_TEST_ORIGIN=$(${seth} --to-hex 256)";
rest = envVarTest "rest.sol" ''
export DAPP_TEST_BALANCE=$(${seth} --to-wei 998877665544 ether)
export DAPP_TEST_ADDRESS=$(${seth} --to-hex 256)
export DAPP_TEST_CALLER=$(${seth} --to-hex 100)
export DAPP_TEST_GAS_CREATE=$(${seth} --to-wei 4.20 ether)
export DAPP_TEST_GAS_CALL=$(${seth} --to-wei 0.69 ether)
export DAPP_TEST_NONCE=100
export DAPP_TEST_COINBASE=$(${seth} --to-hex 666)
export DAPP_TEST_NUMBER=420
export DAPP_TEST_TIMESTAMP=69
export DAPP_TEST_GAS_LIMIT=$(${seth} --to-wei 4206966 ether)
export DAPP_TEST_GAS_PRICE=100
export DAPP_TEST_DIFFICULTY=600
'';
};

shouldFail = let
fail = match : runTest {
dir = ./fail;
shouldFail = true;
name = "dappTestsShouldFail-${match}";
dappFlags = "--match ${match} --smttimeout 600000";
testFlags = "--match ${match} --smttimeout 600000";
};
in pkgs.recurseIntoAttrs {
prove-add = fail "prove_add";
Expand All @@ -155,7 +183,7 @@ in
solc = solc-0_6_7;
src = dss-src;
name = "dss";
dappFlags = "--match '[^dai].t.sol'";
testFlags = "--match '[^dai].t.sol'";
deps = [ ds-test ds-token ds-value ];
};
}
8 changes: 8 additions & 0 deletions src/dapp-tests/env/origin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import "ds-test/test.sol";

contract Env is DSTest {
// DAPP_TEST_ORIGIN
function testOrigin() public {
assertEq(tx.origin, address(256));
}
}
72 changes: 72 additions & 0 deletions src/dapp-tests/env/rest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import "ds-test/test.sol";

contract Env is DSTest {
uint creationGas;
constructor() public {
creationGas = gasleft();
}

// TODO: why does this fail when address == 0?
// DAPP_TEST_BALANCE
function testBalance() public {
assertEq(address(this).balance, 998877665544 ether);
}
// DAPP_TEST_ADDRESS
function testAddress() public {
assertEq(address(this), address(256));
}
// DAPP_TEST_NONCE
// we can't test the nonce directly, but can instead check the address of a newly deployed contract
function testNonce() public {
uint8 nonce = 100;
bytes memory payload = abi.encodePacked(hex"d694", address(this), nonce);

address expected = address(uint160(uint256(keccak256(payload))));
address actual = address(new Trivial());
assertEq(actual, expected);

}
// DAPP_TEST_CALLER
function testCaller() public {
assertEq(msg.sender, address(100));
}
// DAPP_TEST_GAS_CREATE
function testGasCreate() public {
// we can't be exact since we had to spend some gas to write to storage...
assertLt(creationGas, 4.20 ether);
assertGt(creationGas, 4.1999999999999 ether);
}
// DAPP_TEST_GAS_CALL
function testGasCall() public {
uint gas = gasleft();
// we can't be exact since we had to spend some gas to get here...
assertLt(gas, 0.69 ether);
assertGt(gas, 0.689999999999999 ether);
}
// DAPP_TEST_COINBASE
function testCoinbase() public {
assertEq(block.coinbase, address(666));
}
// DAPP_TEST_NUMBER
function testBlockNumber() public {
assertEq(block.number, 420);
}
// DAPP_TEST_TIMESTAMP
function testTimestamp() public {
assertEq(block.timestamp, 69);
}
// DAPP_TEST_GAS_LIMIT
function testGasLimit() public {
assertEq(block.gaslimit, 4206966 ether);
}
// DAPP_TEST_GAS_PRICE
function testGasPrice() public {
assertEq(tx.gasprice, 100);
}
// DAPP_TEST_DIFFICULTY
function testDifficulty() public {
assertEq(block.difficulty, 600);
}
}

contract Trivial {}
65 changes: 65 additions & 0 deletions src/dapp-tests/pass/cheatCodes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,29 @@ interface Hevm {
function sign(uint256,bytes32) external returns (uint8,bytes32,bytes32);
function addr(uint256) external returns (address);
function ffi(string[] calldata) external returns (bytes memory);
function file(bytes32,uint) external;
function file(bytes32,address) external;
function file(bytes32,address,uint) external;
function look(bytes32) external returns (uint);
function look(bytes32,address) external returns (uint);
function replace(address,bytes calldata) external;
}

contract HasStorage {
uint slot0 = 10;
}

contract Old {
uint constant public x = 10;
}

contract New {
uint constant public x = 100;
}

contract CheatCodes is DSTest {
address store = address(new HasStorage());
Old target = new Old();
Hevm hevm = Hevm(HEVM_ADDRESS);

function test_warp_concrete(uint128 jump) public {
Expand Down Expand Up @@ -87,4 +102,54 @@ contract CheatCodes is DSTest {
(string memory output) = abi.decode(hevm.ffi(inputs), (string));
assertEq(output, "acab");
}

function testNonce(address who, uint n) public {
hevm.file("nonce", who, n);
assertEq(hevm.look("nonce", who), n);
}

function testBalance(address who, uint bal) public {
hevm.file("balance", who, bal);
assertEq(who.balance, bal);
}

function testReplace() public {
Copy link
Contributor Author

@d-xo d-xo Aug 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no idea why at all, but if I construct the target contract directly with the code below in the test instead of in the constructor this test always fails (and it does so before reaching the call to the cheatcode).... 🤔

Suggested change
function testReplace() public {
function testReplace() public {
Old target = new Old();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, strange indeed. Does it revert or what is the error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failure: testReplace
  src/env.sol:CheatCodes
   ├╴constructor
   │  └╴create <unknown contract>@0xCe71065D4017F316EC606Fe4422e11eB2c47c246 <no source map>
   │     └╴← 63 bytes of code
   └╴testReplace()
      └╴error Revert 0x <no source map>

hevm.replace(address(target), type(New).runtimeCode);
assertEq(New(address(target)).x(), 100);
}

function proveCaller(address who) public {
hevm.file("caller", who);
assertEq(msg.sender, who);
}

function testOrigin(address who) public {
hevm.file("origin", who);
assertEq(tx.origin, who);
}

function testCoinbase(address who) public {
hevm.file("coinbase", who);
assertEq(block.coinbase, who);
}

function testGasPrice(uint price) public {
hevm.file("gasPrice", price);
assertEq(tx.gasprice, price);
}

function testTxGasLimit(uint limit) public {
hevm.file("txGasLimit", limit);
assertEq(hevm.look("txGasLimit"), limit);
}

function testBlockGasLimit(uint limit) public {
hevm.file("blockGasLimit", limit);
assertEq(block.gaslimit, limit);
}

function testDifficulty(uint difficulty) public {
hevm.file(bytes32("difficulty"), difficulty);
assertEq(block.difficulty, difficulty);
}
}
4 changes: 4 additions & 0 deletions src/hevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Added
Copy link
Contributor Author

@d-xo d-xo Aug 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: document new cheatcodes....


- A new configuration variable `DAPP_TEST_NONCE` has been added that allows control over the nonce of the testing contract

### Changed

- The configuration variable `DAPP_TEST_BALANCE_CREATE` has been renamed to `DAPP_TEST_BALANCE`
Expand Down
1 change: 1 addition & 0 deletions src/hevm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ These environment variables can be used to control block parameters:
| `DAPP_TEST_GAS_CREATE` | `0xffffffffffff` | The gas to provide when creating the testing contract |
| `DAPP_TEST_GAS_CALL` | `0xffffffffffff` | The gas to provide to each call made to the testing contract |
| `DAPP_TEST_BALANCE` | `0xffffffffffffffffffffffff` | The balance to provide to `DAPP_TEST_ADDRESS` |
| `DAPP_TEST_NONCE ` | `1` | The initial nonce to use for `DAPP_TEST_ADDRESS` |
| `DAPP_TEST_COINBASE` | `0x0000000000000000000000000000000000000000` | The coinbase address. Will be set to the coinbase for the block at `DAPP_TEST_NUMBER` if rpc is enabled |
| `DAPP_TEST_NUMBER` | `0` | The block number. Will be set to the latest block if rpc is enabled |
| `DAPP_TEST_TIMESTAMP` | `0` | The block timestamp. Will be set to the timestamp for the block at `DAPP_TEST_NUMBER` if rpc is enabled |
Expand Down
Loading