-
Notifications
You must be signed in to change notification settings - Fork 75
feat: support state overrides in eth_call #337
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
base: main
Are you sure you want to change the base?
Changes from all commits
c1b2ffd
9f87dc7
8ce5ecc
7586ebe
9ea101b
a6d0dc1
f408fde
625649e
89d2b8e
8b0a953
471b279
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,16 @@ | ||
package types | ||
|
||
import ( | ||
"fmt" | ||
"math/big" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
"github.com/ethereum/go-ethereum/core/tracing" | ||
ethtypes "github.com/ethereum/go-ethereum/core/types" | ||
"github.com/holiman/uint256" | ||
|
||
"github.com/cosmos/evm/x/vm/statedb" | ||
) | ||
|
||
// Copied the Account and StorageResult types since they are registered under an | ||
|
@@ -55,6 +60,42 @@ type RPCTransaction struct { | |
// StateOverride is the collection of overridden accounts. | ||
type StateOverride map[common.Address]OverrideAccount | ||
|
||
// Apply overrides the fields of specified accounts into the given state. | ||
func (diff *StateOverride) Apply(db *statedb.StateDB) error { | ||
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. As of go-ethereum v1.15.10, the StateOverride.Apply method includes logic for handling precompile addresses. From a compatibility standpoint, it may be best to either align the logic to match exactly, or alternatively, directly import the override package from Geth and use its |
||
if db == nil || diff == nil { | ||
return nil | ||
} | ||
for addr, account := range *diff { | ||
// Override account nonce. | ||
if account.Nonce != nil { | ||
db.SetNonce(addr, uint64(*account.Nonce), tracing.NonceChangeUnspecified) | ||
} | ||
// Override account(contract) code. | ||
if account.Code != nil { | ||
db.SetCode(addr, *account.Code) | ||
} | ||
// Override account balance. | ||
if account.Balance != nil && *account.Balance != nil { | ||
u256Balance, _ := uint256.FromBig((*big.Int)(*account.Balance)) | ||
db.SetBalance(addr, u256Balance) | ||
} | ||
if account.State != nil && account.StateDiff != nil { | ||
return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) | ||
} | ||
// Replace entire state if caller requires. | ||
if account.State != nil { | ||
db.SetStorage(addr, *account.State) | ||
} | ||
// Apply state diff into specified accounts. | ||
if account.StateDiff != nil { | ||
for key, value := range *account.StateDiff { | ||
db.SetState(addr, key, value) | ||
} | ||
Comment on lines
+91
to
+93
Check warningCode scanning / CodeQL Iteration over map Warning
Iteration over map may be a possible source of non-determinism
|
||
} | ||
} | ||
return nil | ||
} | ||
|
||
// OverrideAccount indicates the overriding fields of account during the execution of | ||
// a message call. | ||
// Note, state and stateDiff can't be specified at the same time. If state is | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -425,11 +425,24 @@ func (s *TestSuite) TestDoCall() { | |
argsBz, err := json.Marshal(callArgs) | ||
s.Require().NoError(err) | ||
|
||
overrides := json.RawMessage(`{ | ||
"` + toAddr.Hex() + `": { | ||
"balance": "0x1000000000000000000", | ||
"nonce": "0x1", | ||
"code": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c8063c6888fa11461003b578063c8e7ca2e14610057575b600080fd5b610055600480360381019061005091906100a3565b610075565b005b61005f61007f565b60405161006c91906100e1565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b61009d8161008a565b81146100a857600080fd5b50565b6000813590506100ba81610094565b92915050565b6000602082840312156100d6576100d5610085565b5b60006100e4848285016100ab565b91505092915050565b6100f68161008a565b82525050565b600060208201905061011160008301846100ed565b9291505056fea2646970667358221220c7d2d7c0b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b264736f6c634300080a0033", | ||
"storage": { | ||
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x123" | ||
} | ||
} | ||
}`) | ||
invalidOverrides := json.RawMessage(`{"invalid": json}`) | ||
emptyOverrides := json.RawMessage(`{}`) | ||
testCases := []struct { | ||
name string | ||
registerMock func() | ||
blockNum rpctypes.BlockNumber | ||
callArgs evmtypes.TransactionArgs | ||
overrides *json.RawMessage | ||
expEthTx *evmtypes.MsgEthereumTxResponse | ||
expPass bool | ||
}{ | ||
|
@@ -444,6 +457,7 @@ func (s *TestSuite) TestDoCall() { | |
}, | ||
rpctypes.BlockNumber(1), | ||
callArgs, | ||
nil, | ||
&evmtypes.MsgEthereumTxResponse{}, | ||
false, | ||
}, | ||
|
@@ -458,6 +472,67 @@ func (s *TestSuite) TestDoCall() { | |
}, | ||
rpctypes.BlockNumber(1), | ||
callArgs, | ||
nil, | ||
&evmtypes.MsgEthereumTxResponse{}, | ||
true, | ||
}, | ||
{ | ||
"pass - With state overrides", | ||
func() { | ||
client := s.backend.ClientCtx.Client.(*mocks.Client) | ||
QueryClient := s.backend.QueryClient.QueryClient.(*mocks.EVMQueryClient) | ||
_, err := RegisterBlock(client, 1, bz) | ||
s.Require().NoError(err) | ||
expected := &evmtypes.EthCallRequest{ | ||
Args: argsBz, | ||
ChainId: s.backend.EvmChainID.Int64(), | ||
Overrides: overrides, | ||
} | ||
RegisterEthCall(QueryClient, expected) | ||
}, | ||
rpctypes.BlockNumber(1), | ||
callArgs, | ||
&overrides, | ||
&evmtypes.MsgEthereumTxResponse{}, | ||
true, | ||
}, | ||
{ | ||
"fail - Invalid state overrides JSON", | ||
func() { | ||
client := s.backend.ClientCtx.Client.(*mocks.Client) | ||
QueryClient := s.backend.QueryClient.QueryClient.(*mocks.EVMQueryClient) | ||
_, err := RegisterBlock(client, 1, bz) | ||
s.Require().NoError(err) | ||
expected := &evmtypes.EthCallRequest{ | ||
Args: argsBz, | ||
ChainId: s.backend.EvmChainID.Int64(), | ||
Overrides: invalidOverrides, | ||
} | ||
RegisterEthCallError(QueryClient, expected) | ||
}, | ||
rpctypes.BlockNumber(1), | ||
callArgs, | ||
&invalidOverrides, | ||
&evmtypes.MsgEthereumTxResponse{}, | ||
false, | ||
}, | ||
{ | ||
"pass - Empty state overrides", | ||
func() { | ||
client := s.backend.ClientCtx.Client.(*mocks.Client) | ||
QueryClient := s.backend.QueryClient.QueryClient.(*mocks.EVMQueryClient) | ||
_, err := RegisterBlock(client, 1, bz) | ||
s.Require().NoError(err) | ||
expected := &evmtypes.EthCallRequest{ | ||
Args: argsBz, | ||
ChainId: s.backend.EvmChainID.Int64(), | ||
Overrides: emptyOverrides, | ||
} | ||
RegisterEthCall(QueryClient, expected) | ||
}, | ||
rpctypes.BlockNumber(1), | ||
callArgs, | ||
&emptyOverrides, | ||
&evmtypes.MsgEthereumTxResponse{}, | ||
true, | ||
}, | ||
|
@@ -468,9 +543,10 @@ func (s *TestSuite) TestDoCall() { | |
s.SetupTest() // reset test and queries | ||
tc.registerMock() | ||
|
||
msgEthTx, err := s.backend.DoCall(tc.callArgs, tc.blockNum) | ||
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. It would be good to add a simple test case to check whether the expected result is returned when the state overrides input is set. |
||
msgEthTx, err := s.backend.DoCall(tc.callArgs, tc.blockNum, tc.overrides) | ||
|
||
if tc.expPass { | ||
s.Require().NoError(err) | ||
s.Require().Equal(tc.expEthTx, msgEthTx) | ||
} else { | ||
s.Require().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.
nit: formatting of the arguments - can we make them vertical?