From 9f573690a6eb8957d902c69915ea06514f09c20f Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Wed, 17 Jun 2020 14:23:36 -0400 Subject: [PATCH] implement eth_exportAccount (#331) --- rpc/eth_api.go | 13 +++++++ tests/rpc_test.go | 75 ++++++++++++++++++++++++++++++++++-- x/evm/alias.go | 1 + x/evm/keeper/querier.go | 29 ++++++++++++++ x/evm/keeper/querier_test.go | 1 + x/evm/types/querier.go | 3 ++ x/evm/types/state_object.go | 4 +- x/evm/types/statedb.go | 3 +- 8 files changed, 123 insertions(+), 6 deletions(-) diff --git a/rpc/eth_api.go b/rpc/eth_api.go index 6586675c..ede966dc 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -264,6 +264,19 @@ func (e *PublicEthAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, return e.backend.GetTransactionLogs(txHash) } +// ExportAccount exports an account's balance, code, and storage at the given block number +// TODO: deprecate this once the export genesis command works +func (e *PublicEthAPI) ExportAccount(address common.Address, blockNumber BlockNumber) (string, error) { + ctx := e.cliCtx.WithHeight(blockNumber.Int64()) + + res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryExportAccount, address.Hex()), nil) + if err != nil { + return "", err + } + + return string(res), nil +} + // Sign signs the provided data using the private key of address via Geth's signature standard. func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { // TODO: Change this functionality to find an unlocked account by address diff --git a/tests/rpc_test.go b/tests/rpc_test.go index a677a553..4da6d83e 100644 --- a/tests/rpc_test.go +++ b/tests/rpc_test.go @@ -17,6 +17,7 @@ import ( "math/big" "net/http" "os" + "strings" "testing" "time" @@ -28,6 +29,7 @@ import ( "github.com/cosmos/ethermint/rpc" "github.com/cosmos/ethermint/version" + "github.com/cosmos/ethermint/x/evm/types" ) const ( @@ -452,18 +454,21 @@ func deployTestContractWithFunction(t *testing.T) hexutil.Bytes { // contract Test { // event Hello(uint256 indexed world); - // event Test(uint256 indexed a, uint256 indexed b); + // event TestEvent(uint256 indexed a, uint256 indexed b); + + // uint256 myStorage; // constructor() public { // emit Hello(17); // } // function test(uint256 a, uint256 b) public { - // emit Test(a, b); + // myStorage = a; + // emit TestEvent(a, b); // } // } - bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260c98061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b80827f91916a5e2c96453ddf6b585497262675140eb9f7a774095fb003d93e6dc6921660405160405180910390a3505056fea265627a7a72315820ef746422e676b3ed22147cd771a6f689e7c33ef17bf5cd91921793b5dd01e3e064736f6c63430005110032" + bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260d08061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032" from := getAddress(t) @@ -686,6 +691,70 @@ func TestEth_EstimateGas(t *testing.T) { require.Equal(t, hexutil.Bytes{0xf7, 0xa6}, gas) } +func TestEth_ExportAccount(t *testing.T) { + param := []string{} + param = append(param, "0x1122334455667788990011223344556677889900") + param = append(param, "latest") + rpcRes := call(t, "eth_exportAccount", param) + + var res string + err := json.Unmarshal(rpcRes.Result, &res) + require.NoError(t, err) + + var account types.GenesisAccount + err = json.Unmarshal([]byte(res), &account) + require.NoError(t, err) + + require.Equal(t, "0x1122334455667788990011223344556677889900", account.Address.Hex()) + require.Equal(t, big.NewInt(0), account.Balance) + require.Equal(t, hexutil.Bytes(nil), account.Code) + require.Equal(t, []types.GenesisStorage(nil), account.Storage) +} + +func TestEth_ExportAccount_WithStorage(t *testing.T) { + hash := deployTestContractWithFunction(t) + receipt := waitForReceipt(t, hash) + addr := receipt["contractAddress"].(string) + + // call function to set storage + calldata := "0xeb8ac92100000000000000000000000000000000000000000000000000000000000000630000000000000000000000000000000000000000000000000000000000000000" + + from := getAddress(t) + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", from) + param[0]["to"] = addr + //param[0]["value"] = "0x1" + param[0]["data"] = calldata + rpcRes := call(t, "eth_sendTransaction", param) + + var txhash hexutil.Bytes + err := json.Unmarshal(rpcRes.Result, &txhash) + require.NoError(t, err) + waitForReceipt(t, txhash) + + // get exported account + eap := []string{} + eap = append(eap, addr) + eap = append(eap, "latest") + rpcRes = call(t, "eth_exportAccount", eap) + + var res string + err = json.Unmarshal(rpcRes.Result, &res) + require.NoError(t, err) + + var account types.GenesisAccount + err = json.Unmarshal([]byte(res), &account) + require.NoError(t, err) + + // deployed bytecode + bytecode := ethcmn.FromHex("0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032") + require.Equal(t, addr, strings.ToLower(account.Address.Hex())) + require.Equal(t, big.NewInt(0), account.Balance) + require.Equal(t, hexutil.Bytes(bytecode), account.Code) + require.NotEqual(t, []types.GenesisStorage(nil), account.Storage) +} + func TestEth_GetBlockByNumber(t *testing.T) { param := []interface{}{"0x1", false} rpcRes := call(t, "eth_getBlockByNumber", param) diff --git a/x/evm/alias.go b/x/evm/alias.go index 1d336fcd..ddedf1e5 100644 --- a/x/evm/alias.go +++ b/x/evm/alias.go @@ -21,6 +21,7 @@ const ( QueryBloom = types.QueryBloom QueryLogs = types.QueryLogs QueryAccount = types.QueryAccount + QueryExportAccount = types.QueryExportAccount ) // nolint diff --git a/x/evm/keeper/querier.go b/x/evm/keeper/querier.go index 13c9e140..066a0089 100644 --- a/x/evm/keeper/querier.go +++ b/x/evm/keeper/querier.go @@ -42,6 +42,8 @@ func NewQuerier(keeper Keeper) sdk.Querier { return queryLogs(ctx, keeper) case types.QueryAccount: return queryAccount(ctx, path, keeper) + case types.QueryExportAccount: + return queryExportAccount(ctx, path, keeper) default: return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown query endpoint") } @@ -195,3 +197,30 @@ func queryAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) } return bz, nil } + +func queryExportAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) { + addr := ethcmn.HexToAddress(path[1]) + + var storage []types.GenesisStorage + err := keeper.CommitStateDB.ForEachStorage(addr, func(key, value ethcmn.Hash) bool { + storage = append(storage, types.NewGenesisStorage(key, value)) + return false + }) + if err != nil { + return nil, err + } + + res := types.GenesisAccount{ + Address: addr, + Balance: keeper.GetBalance(ctx, addr), + Code: keeper.GetCode(ctx, addr), + Storage: storage, + } + + bz, err := codec.MarshalJSONIndent(keeper.cdc, res) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + + return bz, nil +} diff --git a/x/evm/keeper/querier_test.go b/x/evm/keeper/querier_test.go index d4d115f0..048e461d 100644 --- a/x/evm/keeper/querier_test.go +++ b/x/evm/keeper/querier_test.go @@ -29,6 +29,7 @@ func (suite *KeeperTestSuite) TestQuerier() { // {"logs bloom", []string{types.QueryLogsBloom, "0x0"}, func() {}, true}, {"logs", []string{types.QueryLogs, "0x0"}, func() {}, true}, {"account", []string{types.QueryAccount, "0x0"}, func() {}, true}, + {"exportAccount", []string{types.QueryExportAccount, "0x0"}, func() {}, true}, {"unknown request", []string{"other"}, func() {}, false}, } diff --git a/x/evm/types/querier.go b/x/evm/types/querier.go index fe6c369c..ba40665b 100644 --- a/x/evm/types/querier.go +++ b/x/evm/types/querier.go @@ -19,6 +19,7 @@ const ( QueryBloom = "bloom" QueryLogs = "logs" QueryAccount = "account" + QueryExportAccount = "exportAccount" ) // QueryResProtocolVersion is response type for protocol version query @@ -99,3 +100,5 @@ type QueryResAccount struct { CodeHash []byte `json:"codeHash"` Nonce uint64 `json:"nonce"` } + +type QueryResExportAccount = GenesisAccount diff --git a/x/evm/types/state_object.go b/x/evm/types/state_object.go index 8e89ff01..c07adcab 100644 --- a/x/evm/types/state_object.go +++ b/x/evm/types/state_object.go @@ -221,7 +221,7 @@ func (so *stateObject) markSuicided() { // commitState commits all dirty storage to a KVStore. func (so *stateObject) commitState() { ctx := so.stateDB.ctx - store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), KeyPrefixStorage) + store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), AddressStoragePrefix(so.Address())) for key, value := range so.dirtyStorage { delete(so.dirtyStorage, key) @@ -333,7 +333,7 @@ func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) e // otherwise load the value from the KVStore ctx := so.stateDB.ctx - store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), KeyPrefixStorage) + store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), AddressStoragePrefix(so.Address())) rawValue := store.Get(prefixKey.Bytes()) if len(rawValue) > 0 { diff --git a/x/evm/types/statedb.go b/x/evm/types/statedb.go index 0460560b..9f78b1ce 100644 --- a/x/evm/types/statedb.go +++ b/x/evm/types/statedb.go @@ -717,7 +717,8 @@ func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, valu } store := csdb.ctx.KVStore(csdb.storeKey) - iterator := sdk.KVStorePrefixIterator(store, AddressStoragePrefix(so.Address())) + prefix := AddressStoragePrefix(so.Address()) + iterator := sdk.KVStorePrefixIterator(store, prefix) defer iterator.Close() for ; iterator.Valid(); iterator.Next() {