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

add support for prank(sender, origin) and startPrank(sender, origin) cheatcodes #336

Merged
merged 24 commits into from
Aug 13, 2024

Conversation

karmacoma-eth
Copy link
Collaborator

No description provided.

Comment on lines 206 to 210
function check_prank_ConstructorCreate2(address user, bytes32 salt) public {
vm.prank(user);
ConstructorRecorder recorder = new ConstructorRecorder{salt:salt}();
assert(recorder.caller() == user);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

look at us, we do support CREATE2 now 🙌

@karmacoma-eth
Copy link
Collaborator Author

not sure what's going on with CI, some jobs flagging with:

 From https://github.com/Vectorized/multicaller
 * branch            b4a0dd037f1d770b2e9ae0b80bbd989707df43d0 -> FETCH_HEAD
fatal: Unable to find current revision in submodule path '../lib/multicaller/lib/forge-std'
fatal: Failed to recurse into submodule path '../lib/multicaller'
Your project has missing dependencies that could not be installed.
Error: 
failed to resolve file: "/home/runner/work/halmos/halmos/tests/regression/../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol": No such file or directory (os error 2); check configured remappings
	--> /home/runner/work/halmos/halmos/tests/regression/test/Proxy.t.sol
	openzeppelin/proxy/ERC1967/ERC1967Proxy.sol

@karmacoma-eth
Copy link
Collaborator Author

not sure what's going on with CI, some jobs flagging with:

 From https://github.com/Vectorized/multicaller
 * branch            b4a0dd037f1d770b2e9ae0b80bbd989707df43d0 -> FETCH_HEAD
fatal: Unable to find current revision in submodule path '../lib/multicaller/lib/forge-std'
fatal: Failed to recurse into submodule path '../lib/multicaller'
Your project has missing dependencies that could not be installed.
Error: 
failed to resolve file: "/home/runner/work/halmos/halmos/tests/regression/../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol": No such file or directory (os error 2); check configured remappings
	--> /home/runner/work/halmos/halmos/tests/regression/test/Proxy.t.sol
	openzeppelin/proxy/ERC1967/ERC1967Proxy.sol

fixed itself with enough re-running 🤷‍♂️

(it removes the extra dependency on forge-std)
The foundry step is failing with weird errors:

Submodule 'tests/lib/forge-std' (https://github.com/foundry-rs/forge-std) registered for path '../lib/forge-std'
Submodule 'tests/lib/halmos-cheatcodes' (https://github.com/a16z/halmos-cheatcodes) registered for path '../lib/halmos-cheatcodes'
Submodule 'tests/lib/openzeppelin-contracts' (https://github.com/OpenZeppelin/openzeppelin-contracts) registered for path '../lib/openzeppelin-contracts'
Submodule 'tests/lib/solady' (https://github.com/Vectorized/solady) registered for path '../lib/solady'
Submodule 'tests/lib/solmate' (https://github.com/transmissions11/solmate) registered for path '../lib/solmate'
fatal: not a git repository: /home/runner/work/halmos/halmos/tests/lib/forge-std/../../../.git/modules/tests/lib/forge-std
Failed to clone 'tests/lib/forge-std'. Retry scheduled
fatal: destination path '/home/runner/work/halmos/halmos/tests/lib/halmos-cheatcodes' already exists and is not an empty directory.
fatal: clone of 'https://github.com/a16z/halmos-cheatcodes' into submodule path '/home/runner/work/halmos/halmos/tests/lib/halmos-cheatcodes' failed
Failed to clone 'tests/lib/halmos-cheatcodes'. Retry scheduled
Suspecting race conditions here:

```
Build failed: ['forge', 'build', '--ast', '--root', 'tests/regression', '--extra-output', 'storageLayout', 'metadata']
----------------------------- Captured stderr call -----------------------------
Failed to install solc 0.8.26: Text file busy (os error 26)
Error:
Text file busy (os error 26)
```
@@ -53,4 +53,4 @@ jobs:
run: python -m pip install -e .

- name: Run pytest
run: pytest -n 4 -v -k "not long and not ffi" --ignore=tests/lib --halmos-options="-st ${{ matrix.parallel }} --storage-layout ${{ matrix.storage-layout }} --solver-timeout-assertion 0 ${{ inputs.halmos-options }}" ${{ inputs.pytest-options }}
run: pytest -n 1 -v -k "not long and not ffi" --ignore=tests/lib --halmos-options="--debug -st ${{ matrix.parallel }} --storage-layout ${{ matrix.storage-layout }} --solver-timeout-assertion 0 ${{ inputs.halmos-options }}" ${{ inputs.pytest-options }}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

pytest -n 1 is what fixed CI, it seems that the issue was running multiple forge commands in parallel would result in race conditions trying to clone submodules or install solc

src/halmos/sevm.py Outdated Show resolved Hide resolved
src/halmos/sevm.py Outdated Show resolved Hide resolved
src/halmos/sevm.py Outdated Show resolved Hide resolved
@@ -1610,6 +1619,7 @@ def callback(new_ex: Exec, stack, step_id):
new_ex.jumpis = deepcopy(ex.jumpis)
new_ex.symbolic = ex.symbolic
new_ex.prank = deepcopy(ex.prank)
new_ex.origin = ex.origin
Copy link
Collaborator

Choose a reason for hiding this comment

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

nice to restore it here, but don't forget it in other places that restore vm states:

  • in the callback for create():

    halmos/src/halmos/sevm.py

    Lines 1938 to 1944 in 09b6438

    # restore vm state
    new_ex.pgm = ex.pgm
    new_ex.pc = ex.pc
    new_ex.st = deepcopy(ex.st)
    new_ex.jumpis = deepcopy(ex.jumpis)
    new_ex.symbolic = ex.symbolic
    new_ex.prank = deepcopy(ex.prank)
  • at the end of deploy_test():
    # reset vm state
    ex.pc = 0
    ex.st = State()
    ex.context.output = CallOutput()
    ex.jumpis = {}
    ex.prank = Prank()

Copy link
Collaborator

Choose a reason for hiding this comment

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

probably we need more tests to cover these cases:

  • checking tx.origin restored after contract creation
  • checking tx.origin restored after test contract constructor when there is startPrank() but no closing stopPrank() in the constructor

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

tests added in 1ea690c

assert(target.origin() == origin);

// but the inner call is not pranked
checkNotPranked(inner, address(target));
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this also fails in foundry 🤨 is there an issue or is my mental model just wrong?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

update: my mental model was wrong, and so the implementation was wrong too. I checked that all the tests in PrankTest now behave the same in both foundry and halmos (df2c7a3)

@karmacoma-eth karmacoma-eth marked this pull request as draft August 6, 2024 16:36
@karmacoma-eth karmacoma-eth marked this pull request as ready for review August 9, 2024 23:34
Copy link
Collaborator

@daejunpark daejunpark left a comment

Choose a reason for hiding this comment

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

I love the new method of maintaining the caller and origin using the call context and having them seamlessly interact with the prank context. Also appreciate the comprehensive tests! Thanks for the effort!

@daejunpark
Copy link
Collaborator

Note: issues #342 and #343 are orthogonal to this PR, so please feel free to merge this as is, and address them later.

return True
def startPrank(self, sender: Address, origin: Address | None = None) -> bool:
result = self.prank(sender, origin)
self.keep = result if result else self.keep
Copy link
Collaborator

@daejunpark daejunpark Aug 13, 2024

Choose a reason for hiding this comment

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

This is accurate but a bit convoluted. For better code readability, I'd suggest having prank() take an additional argument (defaulting to False if not provided) that is assigned to self.keep. Then, have startPrank() call prank(..., keep=True).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah you're right, in 17eba86

@karmacoma-eth karmacoma-eth enabled auto-merge (squash) August 13, 2024 22:23
@karmacoma-eth karmacoma-eth merged commit f029418 into main Aug 13, 2024
32 checks passed
@karmacoma-eth karmacoma-eth deleted the start-prank-origin branch August 13, 2024 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants