-
Notifications
You must be signed in to change notification settings - Fork 3.9k
l2geth + contracts: standard interface for systems contracts and userland contracts #721
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
Changes from all commits
e2bea4e
cacc968
9cd6ab4
fac76bf
7042711
c98c811
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@eth-optimism/l2geth": patch | ||
| --- | ||
|
|
||
| Return bytes from both ExecutionManager.run and ExecutionManager.simulateMessage and be sure to properly ABI decode the return values and the nested (bool, returndata) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@eth-optimism/contracts": patch | ||
| --- | ||
|
|
||
| Update ABI of simulateMessage to match run |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,13 +17,15 @@ | |
| package vm | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "crypto/rand" | ||
| "encoding/hex" | ||
| "fmt" | ||
| "math/big" | ||
| "strings" | ||
| "sync/atomic" | ||
| "time" | ||
|
|
||
| "github.com/ethereum/go-ethereum/accounts/abi" | ||
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/common/hexutil" | ||
| "github.com/ethereum/go-ethereum/crypto" | ||
|
|
@@ -32,10 +34,66 @@ import ( | |
| "github.com/ethereum/go-ethereum/rollup/dump" | ||
| ) | ||
|
|
||
| // codec is a decoder for the return values of the execution manager. It decodes | ||
| // (bool, bytes) from the bytes that are returned from both | ||
| // `ExecutionManager.run()` and `ExecutionManager.simulateMessage()` | ||
| var codec abi.ABI | ||
|
|
||
| // innerData represents the results returned from the ExecutionManager | ||
| // that are wrapped in `bytes` | ||
| type innerData struct { | ||
| Success bool `abi:"_success"` | ||
| ReturnData []byte `abi:"_returndata"` | ||
| } | ||
|
|
||
| // runReturnData represents the actual return data of the ExecutionManager. | ||
| // It wraps (bool, bytes) in an ABI encoded bytes | ||
| type runReturnData struct { | ||
| ReturnData []byte `abi:"_returndata"` | ||
| } | ||
|
|
||
| // Will be removed when we update EM to return data in `run`. | ||
| var deadPrefix, fortyTwoPrefix, zeroPrefix []byte | ||
|
|
||
| func init() { | ||
| const abidata = ` | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be defined as an interface in the contracts repo and I should be able to import its ABI
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that should be possible if we can add it to the spec |
||
| [ | ||
| { | ||
| "type": "function", | ||
|
||
| "name": "call", | ||
| "constant": true, | ||
| "inputs": [], | ||
| "outputs": [ | ||
| { | ||
| "name": "_success", | ||
| "type": "bool" | ||
| }, | ||
| { | ||
| "name": "_returndata", | ||
| "type": "bytes" | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "type": "function", | ||
| "name": "blob", | ||
| "constant": true, | ||
| "inputs": [], | ||
| "outputs": [ | ||
| { | ||
| "name": "_returndata", | ||
| "type": "bytes" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| ` | ||
|
|
||
| var err error | ||
| codec, err = abi.JSON(strings.NewReader(abidata)) | ||
| if err != nil { | ||
| panic(fmt.Errorf("unable to create abi decoder: %v", err)) | ||
| } | ||
| deadPrefix = hexutil.MustDecode("0xdeaddeaddeaddeaddeaddeaddeaddeaddead") | ||
| zeroPrefix = hexutil.MustDecode("0x000000000000000000000000000000000000") | ||
| fortyTwoPrefix = hexutil.MustDecode("0x420000000000000000000000000000000000") | ||
|
|
@@ -325,39 +383,39 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas | |
|
|
||
| if UsingOVM { | ||
| // OVM_ENABLED | ||
|
|
||
| if evm.depth == 0 { | ||
| // We're back at the root-level message call, so we'll need to modify the return data | ||
| // sent to us by the OVM_ExecutionManager to instead be the intended return data. | ||
|
|
||
| if len(ret) >= 96 { | ||
| // We expect that EOA contracts return at least 96 bytes of data, where the first | ||
| // 32 bytes are the boolean success value and the next 64 bytes are unnecessary | ||
| // ABI encoding data. The actual return data starts at the 96th byte and can be | ||
| // empty. | ||
| success := ret[:32] | ||
| ret = ret[96:] | ||
|
|
||
| if !bytes.Equal(success, AbiBytesTrue) && !bytes.Equal(success, AbiBytesFalse) { | ||
| // If the first 32 bytes not either are the ABI encoding of "true" or "false", | ||
| // then the user hasn't correctly ABI encoded the result. We return the null | ||
| // hex string as a default here (an annoying default that would convince most | ||
| // people to just use the standard form). | ||
| ret = common.FromHex("0x") | ||
| } else if bytes.Equal(success, AbiBytesFalse) { | ||
| // If the first 32 bytes are the ABI encoding of "false", then we need to add an | ||
| // artificial error that represents the revert. | ||
| // Attempt to decode the returndata as as ExecutionManager.run when | ||
| // it is not an `eth_call` and as ExecutionManager.simulateMessage | ||
| // when it is an `eth_call`. If the data is not decodable as ABI | ||
| // encoded bytes, then return nothing. If the data is able to be | ||
| // decoded as bytes, then attempt to decode as (bool, bytes) | ||
| isDecodable := true | ||
| returnData := runReturnData{} | ||
| if err := codec.Unpack(&returnData, "blob", ret); err != nil { | ||
| isDecodable = false | ||
| } | ||
|
|
||
| switch isDecodable { | ||
| case true: | ||
| inner := innerData{} | ||
| // If this fails to decode, the nil values will be set in | ||
| // `inner`, meaning that it will be interpreted as reverted | ||
| // execution with empty returndata | ||
| _ = codec.Unpack(&inner, "call", returnData.ReturnData) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently we decode under the assumption that userland contracts will return (bool, bytes). I'd like to also support (bool) returndata. One way to cheat this is by appending the zero hash to the returndata if you want a hack that will work consistently.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fineeee no hacks. Better just add a second decoding step. |
||
| if !inner.Success { | ||
| err = errExecutionReverted | ||
| } | ||
| } else { | ||
| // User hasn't conformed the standard format, just return "null" for the success | ||
| // (with no return data) to convince them to use the standard. | ||
| ret = common.FromHex("0x") | ||
| ret = inner.ReturnData | ||
| case false: | ||
| ret = []byte{} | ||
| } | ||
| } | ||
|
|
||
| if evm.Context.EthCallSender == nil { | ||
| log.Debug("Reached the end of an OVM execution", "ID", evm.Id, "Return Data", hexutil.Encode(ret), "Error", err) | ||
| } | ||
| if evm.Context.EthCallSender == nil { | ||
| log.Debug("Reached the end of an OVM execution", "ID", evm.Id, "Return Data", hexutil.Encode(ret), "Error", err) | ||
| } | ||
| } | ||
|
|
||
|
|
||
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.
Would note somehow here that this is a convention followed by our contracts, and may not necessarily be the case for arbitrary
enqueues.On the plus side, this return type will let us finally crack #497 by adding the same return type to the
OVM_L2CrossDomainMessenger. :)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 think we should actually talk more about this one -- I don't think it should be necessary for us to
return abi.encode(true, bytes(""))inOVM_L2CrossDomainMessenger. If we add that requirement then we're gonna get a lot of L2 contracts that weirdly return the same thing (just to make the receipts work).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.
@tynes this is what I wanted to talk to you about earlier
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.
A bit confused, does this change imply that consuming contracts would need to follow a specific return value convention? My understanding was that this change only concerns the executionManager's returned values.
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.
There needs to be a strictly defined interface between the system contracts and the userland contracts for consistency purposes. Before this PR, there was not a consistent ABI - two examples are
ExecutionManger.run(...)(bytes)andExecutionManager.simulateMessage(...)(bool,bytes). Therun()bytes had a nested ABI encoded(bool,bytes).We want to index information about the userland contracts and not the system contracts so that when users query for receipt or transaction they get the
fromto be the ESCDA Contract Account. We enforce that a specific ABI encoding is returned from the userland contracts and passed through the system contracts such that geth can receive the data here and parse it.