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

fix: capture response from raw_call #5

Merged
merged 6 commits into from
Apr 14, 2023
Merged
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: 6 additions & 1 deletion contracts/GateSeal.vy
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,16 @@ def seal(_sealables: DynArray[address, MAX_SEALABLES]):
assert sealable in self.sealables, "sealables: includes a non-sealable"

success: bool = False
response: Bytes[32] = b""

# using `raw_call` to catch external revert and continue execution
success = raw_call(
# capturing `response` to keep the compiler from acting out but will not be checking it
# as different sealables may return different values if anything at all
# for details, see https://docs.vyperlang.org/en/stable/built-in-functions.html#raw_call
success, response = raw_call(
TheDZhon marked this conversation as resolved.
Show resolved Hide resolved
sealable,
_abi_encode(SEAL_DURATION_SECONDS, method_id=method_id("pauseFor(uint256)")),
max_outsize=32,
revert_on_failure=False
)

Expand Down
7 changes: 7 additions & 0 deletions contracts/test_helpers/SealableMock.vy
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ def isPaused() -> bool:
return self._is_paused()


@external
def __force_pause_for(_duration: uint256):
# pause ignoring any checks
# required to simulate cases where Sealable is already paused but the seal() reverts
self.resumed_timestamp = block.timestamp + _duration


@external
def pauseFor(_duration: uint256):
assert not self.reverts, "simulating revert"
Expand Down
43 changes: 43 additions & 0 deletions tests/test_gate_seal.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,46 @@ def test_cannot_seal_twice_in_one_tx(gate_seal, sealables, sealing_committee):
gate_seal.seal(sealables, sender=sealing_committee)
with reverts("gate seal: expired"):
gate_seal.seal(sealables, sender=sealing_committee)


def test_raw_call_success_should_be_false_when_sealable_reverts_on_pause(project, deployer, generate_sealables, sealing_committee, seal_duration_seconds, expiry_timestamp):
"""
`raw_call` without `max_outsize` and with `revert_on_failure=True` for some reason returns the boolean value of memory[0] :^)

Which is why we specify `max_outsize=32`, even though don't actually use it.

To test that `success` returns actual value instead of returning bool of memory[0],
we need to pause the contract before the sealing,
so that the condition `success and is_paused()` is false (i.e `False and True`), see GateSeal.vy::seal()

For that, we use `__force_pause_for` on SealableMock to ignore any checks and forcefully pause the contract.
After calling this function, the SealableMock is paused but the call to `pauseFor` will still revert,
thus the returned `success` should be False, the condition fails and the call reverts altogether.

Without `max_outsize=32`, the transaction would not revert.
"""

unpausable = False
should_revert = True
# we'll use only 1 sealable
sealables = generate_sealables(1, unpausable, should_revert)

# deploy GateSeal
gate_seal = project.GateSeal.deploy(
sealing_committee,
seal_duration_seconds,
sealables,
expiry_timestamp,
sender=deployer,
)

# making sure we have the right contract
assert gate_seal.get_sealables() == sealables

# forcefully pause the sealable before sealing
sealables[0].__force_pause_for(seal_duration_seconds, sender=deployer)
assert sealables[0].isPaused(), "should be paused now"

# seal() should revert because `raw_call` to sealable returns `success=False`, even though isPaused() is True.
with reverts("0"):
gate_seal.seal(sealables, sender=sealing_committee)