diff --git a/Makefile b/Makefile index 994cbcb4e7..3e8b893c57 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ GithubTop=github.com -Version=v1.2.2 +Version=v1.2.3 CosmosSDK=v0.39.2 Tendermint=v0.33.9 Iavl=v0.14.3 diff --git a/app/rpc/namespaces/eth/api.go b/app/rpc/namespaces/eth/api.go index 8e4268c3a1..19d953886a 100644 --- a/app/rpc/namespaces/eth/api.go +++ b/app/rpc/namespaces/eth/api.go @@ -800,8 +800,8 @@ func (api *PublicEthereumAPI) SendRawTransaction(data hexutil.Bytes) (common.Has } func (api *PublicEthereumAPI) buildKey(args rpctypes.CallArgs) common.Hash { - latest, e := api.wrappedBackend.GetLatestBlockNumber() - if e != nil { + latest, err := api.wrappedBackend.GetLatestBlockNumber() + if err != nil { return common.Hash{} } return sha256.Sum256([]byte(args.String() + strconv.Itoa(int(latest)))) @@ -833,19 +833,28 @@ func (api *PublicEthereumAPI) addCallCache(key common.Hash, data []byte) { } // Call performs a raw contract call. -func (api *PublicEthereumAPI) Call(args rpctypes.CallArgs, blockNrOrHash rpctypes.BlockNumberOrHash, _ *map[common.Address]rpctypes.Account) (hexutil.Bytes, error) { +func (api *PublicEthereumAPI) Call(args rpctypes.CallArgs, blockNrOrHash rpctypes.BlockNumberOrHash, overrides *evmtypes.StateOverrides) (hexutil.Bytes, error) { monitor := monitor.GetMonitor("eth_call", api.logger, api.Metrics).OnBegin() defer monitor.OnEnd("args", args, "block number", blockNrOrHash) - key := api.buildKey(args) - cacheData, ok := api.getFromCallCache(key) - if ok { - return cacheData, nil + + if overrides != nil { + if err := overrides.Check(); err != nil { + return nil, err + } } + var key common.Hash + if overrides == nil { + key = api.buildKey(args) + if cacheData, ok := api.getFromCallCache(key); ok { + return cacheData, nil + } + } + blockNr, err := api.backend.ConvertToBlockNumber(blockNrOrHash) if err != nil { return nil, err } - simRes, err := api.doCall(args, blockNr, big.NewInt(ethermint.DefaultRPCGasLimit), false) + simRes, err := api.doCall(args, blockNr, big.NewInt(ethermint.DefaultRPCGasLimit), false, overrides) if err != nil { return []byte{}, TransformDataError(err, "eth_call") } @@ -854,12 +863,14 @@ func (api *PublicEthereumAPI) Call(args rpctypes.CallArgs, blockNrOrHash rpctype if err != nil { return []byte{}, TransformDataError(err, "eth_call") } - api.addCallCache(key, data.Ret) + if overrides == nil { + api.addCallCache(key, data.Ret) + } return data.Ret, nil } // MultiCall performs multiple raw contract call. -func (api *PublicEthereumAPI) MultiCall(args []rpctypes.CallArgs, blockNr rpctypes.BlockNumber, _ *map[common.Address]rpctypes.Account) ([]hexutil.Bytes, error) { +func (api *PublicEthereumAPI) MultiCall(args []rpctypes.CallArgs, blockNr rpctypes.BlockNumber, _ *[]evmtypes.StateOverrides) ([]hexutil.Bytes, error) { if !viper.GetBool(FlagEnableMultiCall) { return nil, errors.New("the method is not allowed") } @@ -882,9 +893,13 @@ func (api *PublicEthereumAPI) MultiCall(args []rpctypes.CallArgs, blockNr rpctyp // DoCall performs a simulated call operation through the evmtypes. It returns the // estimated gas used on the operation or an error if fails. func (api *PublicEthereumAPI) doCall( - args rpctypes.CallArgs, blockNum rpctypes.BlockNumber, globalGasCap *big.Int, isEstimate bool, + args rpctypes.CallArgs, + blockNum rpctypes.BlockNumber, + globalGasCap *big.Int, + isEstimate bool, + overrides *evmtypes.StateOverrides, ) (*sdk.SimulationResponse, error) { - + var err error clientCtx := api.clientCtx // pass the given block height to the context if the height is not pending or latest if !(blockNum == rpctypes.PendingBlockNumber || blockNum == rpctypes.LatestBlockNumber) { @@ -934,11 +949,16 @@ func (api *PublicEthereumAPI) doCall( // Create new call message msg := evmtypes.NewMsgEthereumTx(nonce, args.To, value, gas, gasPrice, data) - + var overridesBytes []byte + if overrides != nil { + if overridesBytes, err = overrides.GetBytes(); err != nil { + return nil, fmt.Errorf("fail to encode overrides") + } + } sim := api.evmFactory.BuildSimulator(api) //only worked when fast-query has been enabled if sim != nil { - return sim.DoCall(msg, addr.String()) + return sim.DoCall(msg, addr.String(), overridesBytes) } //Generate tx to be used to simulate (signature isn't needed) @@ -957,11 +977,27 @@ func (api *PublicEthereumAPI) doCall( if err != nil { return nil, err } - // Transaction simulation through query. only pass from when eth_estimateGas. // eth_call's from maybe nil - simulatePath := fmt.Sprintf("app/simulate/%s", addr.String()) - res, _, err := clientCtx.QueryWithData(simulatePath, txBytes) + var simulatePath string + var queryData []byte + if overrides != nil { + simulatePath = fmt.Sprintf("app/simulateWithOverrides/%s", addr.String()) + queryOverridesData := sdk.SimulateData{ + TxBytes: txBytes, + OverridesBytes: overridesBytes, + } + queryData, err = json.Marshal(queryOverridesData) + if err != nil { + return nil, fmt.Errorf("fail to encode queryData for simulateWithOverrides") + } + + } else { + simulatePath = fmt.Sprintf("app/simulate/%s", addr.String()) + queryData = txBytes + } + + res, _, err := clientCtx.QueryWithData(simulatePath, queryData) if err != nil { return nil, err } @@ -981,7 +1017,7 @@ func (api *PublicEthereumAPI) EstimateGas(args rpctypes.CallArgs) (hexutil.Uint6 monitor := monitor.GetMonitor("eth_estimateGas", api.logger, api.Metrics).OnBegin() defer monitor.OnEnd("args", args) - simResponse, err := api.doCall(args, 0, big.NewInt(ethermint.DefaultRPCGasLimit), true) + simResponse, err := api.doCall(args, 0, big.NewInt(ethermint.DefaultRPCGasLimit), true, nil) if err != nil { return 0, TransformDataError(err, "eth_estimateGas") } diff --git a/app/rpc/namespaces/eth/simulation/evm.go b/app/rpc/namespaces/eth/simulation/evm.go index 600653d034..0cc5c73b05 100644 --- a/app/rpc/namespaces/eth/simulation/evm.go +++ b/app/rpc/namespaces/eth/simulation/evm.go @@ -73,8 +73,11 @@ type EvmSimulator struct { } // DoCall call simulate tx. we pass the sender by args to reduce address convert -func (es *EvmSimulator) DoCall(msg *evmtypes.MsgEthereumTx, sender string) (*sdk.SimulationResponse, error) { +func (es *EvmSimulator) DoCall(msg *evmtypes.MsgEthereumTx, sender string, overridesBytes []byte) (*sdk.SimulationResponse, error) { es.ctx = es.ctx.WithFrom(sender) + if overridesBytes != nil { + es.ctx.SetOverrideBytes(overridesBytes) + } r, e := es.handler(es.ctx, msg) if e != nil { return nil, e diff --git a/app/rpc/types/types.go b/app/rpc/types/types.go index fd3b967d26..bb31fad67e 100644 --- a/app/rpc/types/types.go +++ b/app/rpc/types/types.go @@ -109,20 +109,6 @@ func (ca CallArgs) String() string { return strings.TrimRight(arg, ", ") } -// Account 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 -// set, message execution will only use the data in the given state. Otherwise -// if statDiff is set, all diff will be applied first and then execute the call -// message. -type Account struct { - Nonce *hexutil.Uint64 `json:"nonce"` - Code *hexutil.Bytes `json:"code"` - Balance **hexutil.Big `json:"balance"` - State *map[common.Hash]common.Hash `json:"state"` - StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` -} - // EthHeaderWithBlockHash represents a block header in the Ethereum blockchain with block hash generated from Tendermint Block type EthHeaderWithBlockHash struct { ParentHash common.Hash `json:"parentHash"` diff --git a/libs/cosmos-sdk/baseapp/abci.go b/libs/cosmos-sdk/baseapp/abci.go index afe9b4b1eb..49c8b48763 100644 --- a/libs/cosmos-sdk/baseapp/abci.go +++ b/libs/cosmos-sdk/baseapp/abci.go @@ -1,6 +1,7 @@ package baseapp import ( + "encoding/json" "fmt" "os" "sort" @@ -11,6 +12,7 @@ import ( "time" "github.com/okex/exchain/libs/cosmos-sdk/codec" + "github.com/okex/exchain/libs/cosmos-sdk/types" sdk "github.com/okex/exchain/libs/cosmos-sdk/types" sdkerrors "github.com/okex/exchain/libs/cosmos-sdk/types/errors" "github.com/okex/exchain/libs/iavl" @@ -176,7 +178,6 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc return } - func (app *BaseApp) addCommitTraceInfo() { nodeReadCountStr := strconv.Itoa(app.cms.GetNodeReadCount()) dbReadCountStr := strconv.Itoa(app.cms.GetDBReadCount()) @@ -327,48 +328,55 @@ func (app *BaseApp) Query(req abci.RequestQuery) abci.ResponseQuery { return sdkerrors.QueryResult(sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown query path")) } +func handleSimulate(app *BaseApp, path []string, height int64, txBytes []byte, overrideBytes []byte) abci.ResponseQuery { + // if path contains address, it means 'eth_estimateGas' the sender + hasExtraPaths := len(path) > 2 + var from string + if hasExtraPaths { + if addr, err := sdk.AccAddressFromBech32(path[2]); err == nil { + if err = sdk.VerifyAddressFormat(addr); err == nil { + from = path[2] + } + } + } + tx, err := app.txDecoder(txBytes) + if err != nil { + return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to decode tx")) + } + gInfo, res, err := app.Simulate(txBytes, tx, height, overrideBytes, from) + // if path contains mempool, it means to enable MaxGasUsedPerBlock + // return the actual gasUsed even though simulate tx failed + isMempoolSim := hasExtraPaths && path[2] == "mempool" + if err != nil && !isMempoolSim { + return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to simulate tx")) + } + + simRes := sdk.SimulationResponse{ + GasInfo: gInfo, + Result: res, + } + + return abci.ResponseQuery{ + Codespace: sdkerrors.RootCodespace, + Height: height, + Value: codec.Cdc.MustMarshalBinaryBare(simRes), + } +} func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery { if len(path) >= 2 { switch path[1] { case "simulate": - txBytes := req.Data + return handleSimulate(app, path, req.Height, req.Data, nil) - tx, err := app.txDecoder(txBytes) - if err != nil { - return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to decode tx")) - } - - // if path contains address, it means 'eth_estimateGas' the sender - hasExtraPaths := len(path) > 2 - var from string - if hasExtraPaths { - if addr, err := sdk.AccAddressFromBech32(path[2]); err == nil { - if err = sdk.VerifyAddressFormat(addr); err == nil { - from = path[2] - } - } - } - - gInfo, res, err := app.Simulate(txBytes, tx, req.Height, from) - - // if path contains mempool, it means to enable MaxGasUsedPerBlock - // return the actual gasUsed even though simulate tx failed - isMempoolSim := hasExtraPaths && path[2] == "mempool" - if err != nil && !isMempoolSim { - return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to simulate tx")) + case "simulateWithOverrides": + queryBytes := req.Data + var queryData types.SimulateData + if err := json.Unmarshal(queryBytes, &queryData); err != nil { + return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to decode simulateOverrideData")) } + return handleSimulate(app, path, req.Height, queryData.TxBytes, queryData.OverridesBytes) - simRes := sdk.SimulationResponse{ - GasInfo: gInfo, - Result: res, - } - - return abci.ResponseQuery{ - Codespace: sdkerrors.RootCodespace, - Height: req.Height, - Value: codec.Cdc.MustMarshalBinaryBare(simRes), - } case "trace": tmtx, err := GetABCITx(req.Data) if err != nil { diff --git a/libs/cosmos-sdk/baseapp/baseapp_mode_simulate.go b/libs/cosmos-sdk/baseapp/baseapp_mode_simulate.go index 0c8cf247a7..cf64ba691e 100644 --- a/libs/cosmos-sdk/baseapp/baseapp_mode_simulate.go +++ b/libs/cosmos-sdk/baseapp/baseapp_mode_simulate.go @@ -2,6 +2,7 @@ package baseapp import ( "fmt" + sdkerrors "github.com/okex/exchain/libs/cosmos-sdk/types/errors" tmtypes "github.com/okex/exchain/libs/tendermint/types" ) @@ -19,6 +20,8 @@ func (m *modeHandlerSimulate) handleStartHeight(info *runTxInfo, height int64) e } else { info.ctx = app.getContextForTx(m.mode, info.txBytes) } - + if info.overridesBytes != nil { + info.ctx.SetOverrideBytes(info.overridesBytes) + } return err } diff --git a/libs/cosmos-sdk/baseapp/baseapp_runtx.go b/libs/cosmos-sdk/baseapp/baseapp_runtx.go index 0be36a728f..65a8705ac5 100644 --- a/libs/cosmos-sdk/baseapp/baseapp_runtx.go +++ b/libs/cosmos-sdk/baseapp/baseapp_runtx.go @@ -21,9 +21,10 @@ type runTxInfo struct { startingGas uint64 gInfo sdk.GasInfo - result *sdk.Result - txBytes []byte - tx sdk.Tx + result *sdk.Result + txBytes []byte + tx sdk.Tx + overridesBytes []byte } func (app *BaseApp) runTx(mode runTxMode, diff --git a/libs/cosmos-sdk/baseapp/baseapp_test.go b/libs/cosmos-sdk/baseapp/baseapp_test.go index 42b3fb9758..c8dd9e7a6a 100644 --- a/libs/cosmos-sdk/baseapp/baseapp_test.go +++ b/libs/cosmos-sdk/baseapp/baseapp_test.go @@ -981,13 +981,13 @@ func TestSimulateTx(t *testing.T) { require.Nil(t, err) // simulate a message, check gas reported - gInfo, result, err := app.Simulate(txBytes, tx, 0) + gInfo, result, err := app.Simulate(txBytes, tx, 0, nil) require.NoError(t, err) require.NotNil(t, result) require.Equal(t, gasConsumed, gInfo.GasUsed) // simulate again, same result - gInfo, result, err = app.Simulate(txBytes, tx, 0) + gInfo, result, err = app.Simulate(txBytes, tx, 0, nil) require.NoError(t, err) require.NotNil(t, result) require.Equal(t, gasConsumed, gInfo.GasUsed) diff --git a/libs/cosmos-sdk/baseapp/helpers.go b/libs/cosmos-sdk/baseapp/helpers.go index acda546104..7ac28317f1 100644 --- a/libs/cosmos-sdk/baseapp/helpers.go +++ b/libs/cosmos-sdk/baseapp/helpers.go @@ -15,8 +15,11 @@ func (app *BaseApp) Check(tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { return info.gInfo, info.result, e } -func (app *BaseApp) Simulate(txBytes []byte, tx sdk.Tx, height int64, from ...string) (sdk.GasInfo, *sdk.Result, error) { - info, e := app.runTx(runTxModeSimulate, txBytes, tx, height, from...) +func (app *BaseApp) Simulate(txBytes []byte, tx sdk.Tx, height int64, overridesBytes []byte, from ...string) (sdk.GasInfo, *sdk.Result, error) { + info := &runTxInfo{ + overridesBytes: overridesBytes, + } + e := app.runtxWithInfo(info, runTxModeSimulate, txBytes, tx, height, from...) return info.gInfo, info.result, e } diff --git a/libs/cosmos-sdk/simapp/test_helpers.go b/libs/cosmos-sdk/simapp/test_helpers.go index 1090275484..2ba8aa06cd 100644 --- a/libs/cosmos-sdk/simapp/test_helpers.go +++ b/libs/cosmos-sdk/simapp/test_helpers.go @@ -131,7 +131,7 @@ func SignCheckDeliver( require.Nil(t, err) // Must simulate now as CheckTx doesn't run Msgs anymore - _, res, err := app.Simulate(txBytes, tx, 0) + _, res, err := app.Simulate(txBytes, tx, 0, nil) if expSimPass { require.NoError(t, err) diff --git a/libs/cosmos-sdk/types/context.go b/libs/cosmos-sdk/types/context.go index 1e39334498..2b423967f0 100644 --- a/libs/cosmos-sdk/types/context.go +++ b/libs/cosmos-sdk/types/context.go @@ -47,6 +47,7 @@ type Context struct { cache *Cache trc *trace.Tracer accountCache *AccountCache + overridesBytes []byte // overridesBytes is used to save overrides info, passed from ethCall to x/evm } // Proposed rename, not done to avoid API breakage @@ -129,6 +130,10 @@ func (c *Context) GetToAccountCacheGas() Gas { return c.accountCache.ToAccGotGas } +func (c *Context) OverrideBytes() []byte { + return c.overridesBytes +} + func (c *Context) UpdateFromAccountCache(fromAcc interface{}, fromAccGettedGas Gas) { if c.accountCache != nil { c.accountCache.FromAcc = fromAcc @@ -328,6 +333,10 @@ func (c Context) WithCache(cache *Cache) Context { func (c *Context) IsZero() bool { return c.ms == nil } +func (c *Context) SetOverrideBytes(b []byte) *Context { + c.overridesBytes = b + return c +} // WithValue is deprecated, provided for backwards compatibility // Please use diff --git a/libs/cosmos-sdk/types/querier.go b/libs/cosmos-sdk/types/querier.go new file mode 100644 index 0000000000..e2232973f4 --- /dev/null +++ b/libs/cosmos-sdk/types/querier.go @@ -0,0 +1,6 @@ +package types + +type SimulateData struct { + TxBytes []byte `json:"tx"` + OverridesBytes []byte `json:"overrides"` +} diff --git a/libs/cosmos-sdk/x/mock/test_utils.go b/libs/cosmos-sdk/x/mock/test_utils.go index 3cc0359620..d2dcf48980 100644 --- a/libs/cosmos-sdk/x/mock/test_utils.go +++ b/libs/cosmos-sdk/x/mock/test_utils.go @@ -85,7 +85,7 @@ func SignCheckDeliver( require.Nil(t, err) // Must simulate now as CheckTx doesn't run Msgs anymore - _, res, err := app.Simulate(txBytes, tx, 0) + _, res, err := app.Simulate(txBytes, tx, 0, nil) if expSimPass { require.NoError(t, err) diff --git a/x/evm/types/api.go b/x/evm/types/api.go index ec8fef0c53..557a874e0a 100644 --- a/x/evm/types/api.go +++ b/x/evm/types/api.go @@ -1,8 +1,12 @@ package types import ( + "encoding/json" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/vm" ) @@ -66,3 +70,63 @@ func FormatLogs(logs []vm.StructLog) []*StructLogRes { } return formatted } + +// Account 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 +// set, message execution will only use the data in the given state. Otherwise +// if statDiff is set, all diff will be applied first and then execute the call +// message. +type OverrideAccount struct { + Nonce *hexutil.Uint64 `json:"nonce"` + Code *hexutil.Bytes `json:"code"` + Balance **hexutil.Big `json:"balance"` + State *map[common.Hash]common.Hash `json:"state"` + StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` +} + +// StateOverride is the collection of overridden accounts. +type StateOverrides map[common.Address]OverrideAccount + +// Apply overrides the fields of specified accounts into the given state. +func (diff *StateOverrides) Apply(state *CommitStateDB) { + for addr, account := range *diff { + // Override account nonce. + if account.Nonce != nil { + state.SetNonce(addr, uint64(*account.Nonce)) + } + // Override account(contract) code. + if account.Code != nil { + state.SetCode(addr, *account.Code) + } + // Override account balance. + if account.Balance != nil { + state.SetBalance(addr, (*big.Int)(*account.Balance)) + } + // Replace entire state if caller requires. + if account.State != nil { + state.SetStorage(addr, *account.State) + } + // Apply state diff into specified accounts. + if account.StateDiff != nil { + for key, value := range *account.StateDiff { + state.SetState(addr, key, value) + } + } + } +} +func (diff *StateOverrides) Check() error { + for addr, account := range *diff { + if account.State != nil && account.StateDiff != nil { + return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) + } + } + return nil +} +func (diff *StateOverrides) ToString() (string, error) { + valStr, err := json.Marshal(diff) + return string(valStr), err +} +func (diff *StateOverrides) GetBytes() ([]byte, error) { + return json.Marshal(diff) +} diff --git a/x/evm/types/state_object.go b/x/evm/types/state_object.go index c733ff3ed0..77bc63da44 100644 --- a/x/evm/types/state_object.go +++ b/x/evm/types/state_object.go @@ -101,6 +101,8 @@ type StateObject interface { SetNonce(nonce uint64) Nonce() uint64 + + SetStorage(storage map[ethcmn.Hash]ethcmn.Hash) } // stateObject represents an Ethereum account which is being modified. @@ -115,8 +117,9 @@ type stateObject struct { // unable to deal with database-level errors. Any error that occurs // during a database read is memoized here and will eventually be returned // by StateDB.Commit. - originStorage Storage // Storage cache of original entries to dedup rewrites - dirtyStorage Storage // Storage entries that need to be flushed to disk + originStorage Storage // Storage cache of original entries to dedup rewrites + dirtyStorage Storage // Storage entries that need to be flushed to disk + fakeStorage ethstate.Storage // Fake storage which constructed by caller for debugging purpose. // DB error dbErr error @@ -186,6 +189,11 @@ func (so *stateObject) SetState(db ethstate.Database, key, value ethcmn.Hash) { // setState sets a state with a prefixed key and value to the dirty storage. func (so *stateObject) setState(key, value ethcmn.Hash) { + // If the fake storage is set, put the temporary state update here. + if so.fakeStorage != nil { + so.fakeStorage[key] = value + return + } idx, ok := so.keyToDirtyStorageIndex[key] if ok { so.dirtyStorage[idx].Value = value @@ -280,6 +288,24 @@ func (so *stateObject) setNonce(nonce uint64) { so.account.Sequence = nonce } +// SetStorage replaces the entire state storage with the given one. +// +// After this function is called, all original state will be ignored and state +// lookup only happens in the fake state storage. +// +// Note this function should only be used for debugging purpose. +func (so *stateObject) SetStorage(storage map[ethcmn.Hash]ethcmn.Hash) { + // Allocate fake storage if it's nil. + if so.fakeStorage == nil { + so.fakeStorage = make(ethstate.Storage) + } + for key, value := range storage { + so.fakeStorage[key] = value + } + // Don't bother journal since this function should only be used for + // debugging and the `fake` storage won't be committed to database. +} + // setError remembers the first non-nil error it is called with. func (so *stateObject) setError(err error) { if so.dbErr == nil { @@ -415,6 +441,11 @@ func (so *stateObject) Code(_ ethstate.Database) []byte { // GetState retrieves a value from the account storage trie. Note, the key will // be prefixed with the address of the state object. func (so *stateObject) GetState(db ethstate.Database, key ethcmn.Hash) ethcmn.Hash { + // If the fake storage is set, only lookup the state here(in the debugging mode) + if so.fakeStorage != nil { + return so.fakeStorage[key] + } + prefixKey := so.GetStorageByAddressKey(key.Bytes()) // if we have a dirty value for this state entry, return it @@ -432,6 +463,11 @@ func (so *stateObject) GetState(db ethstate.Database, key ethcmn.Hash) ethcmn.Ha // // NOTE: the key will be prefixed with the address of the state object. func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) ethcmn.Hash { + + // If the fake storage is set, only lookup the state here(in the debugging mode) + if so.fakeStorage != nil { + return so.fakeStorage[key] + } prefixKey := so.GetStorageByAddressKey(key.Bytes()) // if we have the original value cached, return that diff --git a/x/evm/types/state_transition.go b/x/evm/types/state_transition.go index 3e0ada78a0..069acc0580 100644 --- a/x/evm/types/state_transition.go +++ b/x/evm/types/state_transition.go @@ -107,6 +107,19 @@ func (st *StateTransition) newEVM( return vm.NewEVM(blockCtx, txCtx, csdb, config.EthereumConfig(st.ChainID), vmConfig) } +func (st *StateTransition) applyOverrides(ctx sdk.Context, csdb *CommitStateDB) error { + overrideBytes := ctx.OverrideBytes() + if overrideBytes != nil { + var stateOverrides StateOverrides + err := json.Unmarshal(overrideBytes, &stateOverrides) + if err != nil { + return fmt.Errorf("failed to decode stateOverrides") + } + stateOverrides.Apply(csdb) + } + return nil +} + // TransitionDb will transition the state by applying the current transaction and // returning the evm execution result. // NOTE: State transition checks are run during AnteHandler execution. @@ -157,6 +170,11 @@ func (st StateTransition) TransitionDb(ctx sdk.Context, config ChainConfig) (exe analyzer.StopTxLog(tag) } } + if ctx.IsCheckTx() { + if err = st.applyOverrides(ctx, csdb); err != nil { + return + } + } params := csdb.GetParams() diff --git a/x/evm/types/statedb.go b/x/evm/types/statedb.go index aac2dd1b5f..bd3518758b 100644 --- a/x/evm/types/statedb.go +++ b/x/evm/types/statedb.go @@ -14,6 +14,7 @@ import ( "github.com/okex/exchain/libs/cosmos-sdk/store/types" + "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common" ethstate "github.com/ethereum/go-ethereum/core/state" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -260,6 +261,15 @@ func (csdb *CommitStateDB) SetParams(params Params) { GetEvmParamsCache().SetNeedParamsUpdate() } +// SetStorage replaces the entire storage for the specified account with given +// storage. This function should only be used for debugging. +func (csdb *CommitStateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) { + stateObject := csdb.GetOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetStorage(storage) + } +} + // SetBalance sets the balance of an account. func (csdb *CommitStateDB) SetBalance(addr ethcmn.Address, amount *big.Int) { so := csdb.GetOrNewStateObject(addr)