Background
During the audit of Lido's Gate Seals code statemind team identified a weird behavior of the code that uses raw_call
: https://github.com/lidofinance/gate-seals/blob/051593e74df01a4131c485b4fda52e691cd4b7d8/contracts/GateSeal.vy#L164 .
Construction like this:
success = raw_call(
sealable,
_abi_encode(SEAL_DURATION_SECONDS, method_id=method_id("pauseFor(uint256)")),
revert_on_failure=False
)
was not fully documented: https://docs.vyperlang.org/en/v0.3.7/built-in-functions.html#raw_call .
The documentation says that: if max_outsize=0
it should return nothing and then it says that if revert_on_failure=False
it should return a success
flag in the tuple of response, but what if max_outsize=0
and revert_on_failure=False
.
So the team started researching what exactly happened in that case, after some research we found that the Vyper compiler generates the wrong bytecode in that case, it generates the sequence:
CALL // call
MLOAD // MLOAD is wrong since the CALL result is already stored in the stack
Impact
Example of buggy code:
@external
def returnSome(calling: address, a: uint256) -> bool:
success: bool = false
success = raw_call(
calling,
_abi_encode(a, method_id=method_id("a(uint256)")),
revert_on_failure=False
)
any contract that uses the raw_call
with revert_on_failure=False
and max_outsize=0
receives the wrong response from raw_call
. Depending on the memory garbage, the result can be either True
or False
.
Patches
Fix by @charles-cooper vyperlang/vyper@851f7a1
Workarounds
The simple workaround is always to put max_outsize>0
.
Workaround example https://github.com/lidofinance/gate-seals/pull/5/files
References
Lido's fix: https://github.com/lidofinance/gate-seals/pull/5/files
References
Background
During the audit of Lido's Gate Seals code statemind team identified a weird behavior of the code that uses
raw_call
: https://github.com/lidofinance/gate-seals/blob/051593e74df01a4131c485b4fda52e691cd4b7d8/contracts/GateSeal.vy#L164 .Construction like this:
was not fully documented: https://docs.vyperlang.org/en/v0.3.7/built-in-functions.html#raw_call .
The documentation says that: if
max_outsize=0
it should return nothing and then it says that ifrevert_on_failure=False
it should return asuccess
flag in the tuple of response, but what ifmax_outsize=0
andrevert_on_failure=False
.So the team started researching what exactly happened in that case, after some research we found that the Vyper compiler generates the wrong bytecode in that case, it generates the sequence:
Impact
Example of buggy code:
any contract that uses the
raw_call
withrevert_on_failure=False
andmax_outsize=0
receives the wrong response fromraw_call
. Depending on the memory garbage, the result can be eitherTrue
orFalse
.Patches
Fix by @charles-cooper vyperlang/vyper@851f7a1
Workarounds
The simple workaround is always to put
max_outsize>0
.Workaround example https://github.com/lidofinance/gate-seals/pull/5/files
References
Lido's fix: https://github.com/lidofinance/gate-seals/pull/5/files
References