-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat(contracts): native ETH value support for ovmCALL #8
base: regenesis/0.4.0
Are you sure you want to change the base?
Conversation
@@ -13,6 +13,7 @@ import { Lib_ErrorUtils } from "../../libraries/utils/Lib_ErrorUtils.sol"; | |||
import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManager.sol"; | |||
import { iOVM_StateManager } from "../../iOVM/execution/iOVM_StateManager.sol"; | |||
import { iOVM_SafetyChecker } from "../../iOVM/execution/iOVM_SafetyChecker.sol"; | |||
import { IUniswapV2ERC20 } from "../../libraries/standards/IUniswapV2ERC20.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was able to import OVM_ETH
here instead, but could not access OVM_ETH.balanceOf.selector
for whatever reason...
@@ -830,6 +932,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { | |||
(bool success, bytes memory data) = ovmCALL( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just noticed: this could be a staticcall
packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Show resolved
Hide resolved
packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Outdated
Show resolved
Hide resolved
packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Show resolved
Hide resolved
packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Show resolved
Hide resolved
|
||
// Since we transferred it in above and the call reverted, the transfer back should always pass. | ||
// If it did not, this is an OOG, and we have to make the parent out-of-gas as well. | ||
// TODO: should we also enforce there is always enough extra gas to make it past this step? This would mean this condition is only triggered due to some critical bug in the ERC20 implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After talking this through with @maurelian this definitely needs to be fixed. We need to do some sort of enforcement of gasleft beforehand (perhaps line 1112 above becomes (success, returndata) = _contract.call{gas: min(_gasLimit, gasleft() - MIN_GAS_FOR_RETURN_VALUE_BACK) }(_data);
Otherwise, this code path here potentially allows a malicious contract to out-of-gas anyone who calls it with nonzero value, even if they only give the malicious contract a small portion of their own gas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to ^^^ I'm going to give my understanding of the problem we're facing here and the proposed solution because I found it quite difficult to wrap my head around. This is based on conversation with @ben-chain (please correct me if you see any mistakes).
What we're trying to achieve here is to duplicate the behavior of EVM calls that come with attached value. Specifically, this behavior is as follows:
- If making a call with a value > 0 and the call fails, then the value IS NOT transferred to the recipient.
- If making a call with a value > 0 and the call succeeds, then the value IS transferred to the recipient.
- If making a call with a value > 0 and the user is unable to transfer funds (e.g., because they don't have enough balance OR because they don't have enough gas to execute the payment), then the value IS NOT transferred to the recipient AND the call fails with (success=false, returndata=0x). This IS how the EVM behaves. See geth: https://github.com/ethereum/go-ethereum/blob/2dee31930c9977af2a9fcb518fb9838aa609a7cf/core/vm/evm.go#L298
Case (2) is the straightforward happy path case.
Case (3) is also pretty easy to handle by checking for failure of the initial transfer.
Case (1) is slightly harder -- you want to revert the payment if the call fails. You can either do that by reverting the call frame in which the payment was transferred or by transferring the payment back to the original sender. Reverting the call frame here IS technically an option for cases where the transfer fails for some unexpected reason but we can avoid this because OVM_ETH
is trusted. The only case we really need to be careful of is if we were to run out of gas when trying to transfer the ETH back. We could get around this by adding an extra call frame (expensive) or by making sure that the user always provides enough gas to execute both transfer steps when the value is greater than 0.
@@ -1908,6 +2085,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { | |||
(bool success, bytes memory returndata) = ovmCALL( | |||
_transaction.gasLimit, | |||
_transaction.entrypoint, | |||
0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can also remove this 0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on second thought: gonna leave these. Slightly more efficient and minimizes usage of an effectively deprecated code path
4572dfd
to
ea5c006
Compare
MessageContext memory nextMessageContext = messageContext; | ||
nextMessageContext.ovmCALLER = nextMessageContext.ovmADDRESS; | ||
nextMessageContext.ovmADDRESS = _address; | ||
nextMessageContext.isStatic = true; | ||
nextMessageContext.ovmCALLVALUE = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed with @ben-chain, we can either attach the call value to the message context or pass it as a parameter to _callContract
. Attaching the call value to the message context is probably best because it effectively is a message-specific parameter and it must be accessible for the sake of msg.value
.
packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Outdated
Show resolved
Hide resolved
|
||
// Since we transferred it in above and the call reverted, the transfer back should always pass. | ||
// If it did not, this is an OOG, and we have to make the parent out-of-gas as well. | ||
// TODO: should we also enforce there is always enough extra gas to make it past this step? This would mean this condition is only triggered due to some critical bug in the ERC20 implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to ^^^ I'm going to give my understanding of the problem we're facing here and the proposed solution because I found it quite difficult to wrap my head around. This is based on conversation with @ben-chain (please correct me if you see any mistakes).
What we're trying to achieve here is to duplicate the behavior of EVM calls that come with attached value. Specifically, this behavior is as follows:
- If making a call with a value > 0 and the call fails, then the value IS NOT transferred to the recipient.
- If making a call with a value > 0 and the call succeeds, then the value IS transferred to the recipient.
- If making a call with a value > 0 and the user is unable to transfer funds (e.g., because they don't have enough balance OR because they don't have enough gas to execute the payment), then the value IS NOT transferred to the recipient AND the call fails with (success=false, returndata=0x). This IS how the EVM behaves. See geth: https://github.com/ethereum/go-ethereum/blob/2dee31930c9977af2a9fcb518fb9838aa609a7cf/core/vm/evm.go#L298
Case (2) is the straightforward happy path case.
Case (3) is also pretty easy to handle by checking for failure of the initial transfer.
Case (1) is slightly harder -- you want to revert the payment if the call fails. You can either do that by reverting the call frame in which the payment was transferred or by transferring the payment back to the original sender. Reverting the call frame here IS technically an option for cases where the transfer fails for some unexpected reason but we can avoid this because OVM_ETH
is trusted. The only case we really need to be careful of is if we were to run out of gas when trying to transfer the ETH back. We could get around this by adding an extra call frame (expensive) or by making sure that the user always provides enough gas to execute both transfer steps when the value is greater than 0.
@smartcontracts that is a great writeup, right on the money. See here for proposed fix. |
34b479f
to
7a59b2b
Compare
packages/contracts/test/contracts/OVM/accounts/OVM_ECDSAContractAccount.spec.ts
Outdated
Show resolved
Hide resolved
7a59b2b
to
676ef59
Compare
* add ovmCALLVALUE context * add ovmBALANCE * test success and revert cases * test empty contract case * chore: lint
* fix ovmDELEGATECALL type, update tests * add ovmSELFBALANCE * fix ovmDELEGATECALL jumping to CALL * chore: lint
676ef59
to
f3d7b22
Compare
f3d7b22
to
2dc806f
Compare
Lots of good progress. Includes:
Still missing: