From dae19d4714f9fd139a7c19dacfeac41b6e46f386 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Tue, 10 Jan 2023 22:38:08 +0800 Subject: [PATCH 01/41] demonstrate use new cache store clone to integrate native stateful precompiles --- go.mod | 3 +++ go.sum | 8 ++++++-- gomod2nix.toml | 11 +++++++++-- x/evm/keeper/state_transition.go | 5 ++++- x/evm/statedb/native.go | 20 ++++++++++++++++++++ x/evm/statedb/statedb.go | 23 +++++++++++++++++++---- 6 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 x/evm/statedb/native.go diff --git a/go.mod b/go.mod index 8170fbdb13..d81d6b6381 100644 --- a/go.mod +++ b/go.mod @@ -162,6 +162,8 @@ require ( github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/tendermint/btcd v0.1.1 // indirect + github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tidwall/btree v1.5.0 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect @@ -191,6 +193,7 @@ require ( replace ( // use cosmos keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 + github.com/cosmos/cosmos-sdk => github.com/yihuang/cosmos-sdk v0.43.0-beta1.0.20230110141630-be56dc1a6a02 // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.0 diff --git a/go.sum b/go.sum index 965f94f726..ec745574be 100644 --- a/go.sum +++ b/go.sum @@ -230,8 +230,6 @@ github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= github.com/cosmos/cosmos-proto v1.0.0-beta.1 h1:iDL5qh++NoXxG8hSy93FdYJut4XfgbShIocllGaXx/0= github.com/cosmos/cosmos-proto v1.0.0-beta.1/go.mod h1:8k2GNZghi5sDRFw/scPL8gMSowT1vDA+5ouxL8GjaUE= -github.com/cosmos/cosmos-sdk v0.46.8 h1:n3brrFOwTwR9cKpr+k6vf6ryU+4iyzNdKOnQzEP9DwM= -github.com/cosmos/cosmos-sdk v0.46.8/go.mod h1:lg+FqwndbbCYQk1YTUWRDpOsNbQG0nINQqxY7ZnsAP8= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= @@ -977,6 +975,10 @@ github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNG github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= +github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= +github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RMWx1aInLzndwxKalgi5rTqgfXxOxbEI= +github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/tendermint/tendermint v0.34.24 h1:879MKKJWYYPJEMMKME+DWUTY4V9f/FBpnZDI82ky+4k= @@ -1028,6 +1030,8 @@ github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6Ut github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= +github.com/yihuang/cosmos-sdk v0.43.0-beta1.0.20230110141630-be56dc1a6a02 h1:xA0FbP9k+AiQi453ugj1zQk11jN8zDR3+O/ZAvefqPk= +github.com/yihuang/cosmos-sdk v0.43.0-beta1.0.20230110141630-be56dc1a6a02/go.mod h1:q5j3014GTxF1SPLoIR5auNOwnw0a4sflym3YsO8lWhs= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/gomod2nix.toml b/gomod2nix.toml index 65ab566060..85b696b47d 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -96,8 +96,9 @@ schema = 3 version = "v1.0.0-beta.1" hash = "sha256-oATkuj+fM5eBn+ywO+w/tL0AFSIEkx0J3Yz+VhVe0QA=" [mod."github.com/cosmos/cosmos-sdk"] - version = "v0.46.8" - hash = "sha256-jETbixsZq5XJ1pl1XaVJvD5jkcygBrEcHRK/L0+XI68=" + version = "v0.43.0-beta1.0.20230110141630-be56dc1a6a02" + hash = "sha256-xPsC7XYq7Cye1q9lKzi3A6v2+1Uv3JbKW+It08T5/iY=" + replaced = "github.com/yihuang/cosmos-sdk" [mod."github.com/cosmos/go-bip39"] version = "v1.0.0" hash = "sha256-Qm2aC2vaS8tjtMUbHmlBSagOSqbduEEDwc51qvQaBmA=" @@ -447,6 +448,12 @@ schema = 3 [mod."github.com/syndtr/goleveldb"] version = "v1.0.1-0.20210819022825-2ae1ddf74ef7" hash = "sha256-36a4hgVQfwtS2zhylKpQuFhrjdc/Y8pF0dxc26jcZIU=" + [mod."github.com/tendermint/btcd"] + version = "v0.1.1" + hash = "sha256-QQl2GWZaKQtd+LQrgx2unkTLI1qye57fCWwJcmCXT/0=" + [mod."github.com/tendermint/crypto"] + version = "v0.0.0-20191022145703-50d29ede1e15" + hash = "sha256-NkoZ3hKWZt5Hca49I+1g81x1m6aQGELZ/QGLdb3uHm4=" [mod."github.com/tendermint/go-amino"] version = "v0.16.0" hash = "sha256-JW4zO/0vMzf1dXLePOqaMtiLUZgNbuIseh9GV+jQlf0=" diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index ad90dba5fd..6b98d3c97e 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -400,9 +400,12 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, // The dirty states in `StateDB` is either committed or discarded after return if commit { - if err := stateDB.Commit(); err != nil { + dbCtx, err := stateDB.Commit() + if err != nil { return nil, errorsmod.Wrap(err, "failed to commit stateDB") } + + ctx.MultiStore().Restore(dbCtx.MultiStore()) } // calculate a minimum amount of gas to be charged to sender if GasLimit diff --git a/x/evm/statedb/native.go b/x/evm/statedb/native.go new file mode 100644 index 0000000000..8aa1427b4d --- /dev/null +++ b/x/evm/statedb/native.go @@ -0,0 +1,20 @@ +package statedb + +import ( + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/ethereum/go-ethereum/common" +) + +var _ JournalEntry = nativeChange{} + +type nativeChange struct { + snapshot types.CacheMultiStore +} + +func (native nativeChange) Dirtied() *common.Address { + return nil +} + +func (native nativeChange) Revert(s *StateDB) { + s.restoreNativeState(native.snapshot) +} diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 753ec76164..03e71095e6 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -298,6 +298,21 @@ func (s *StateDB) setStateObject(object *stateObject) { s.stateObjects[object.Address()] = object } +func (s *StateDB) restoreNativeState(ms sdk.CacheMultiStore) { + s.ctx = s.ctx.WithMultiStore(ms) +} + +func (s *StateDB) executeNativeAction(action func(ctx sdk.Context) error) error { + snapshot := s.ctx.MultiStore().Clone() + err := action(s.ctx) + if err != nil { + s.restoreNativeState(snapshot) + return err + } + s.journal.append(nativeChange{snapshot: snapshot}) + return nil +} + /* * SETTERS */ @@ -450,19 +465,19 @@ func (s *StateDB) RevertToSnapshot(revid int) { // Commit writes the dirty states to keeper // the StateDB object should be discarded after committed. -func (s *StateDB) Commit() error { +func (s *StateDB) Commit() (sdk.Context, error) { for _, addr := range s.journal.sortedDirties() { obj := s.stateObjects[addr] if obj.suicided { if err := s.keeper.DeleteAccount(s.ctx, obj.Address()); err != nil { - return errorsmod.Wrap(err, "failed to delete account") + return s.ctx, errorsmod.Wrap(err, "failed to delete account") } } else { if obj.code != nil && obj.dirtyCode { s.keeper.SetCode(s.ctx, obj.CodeHash(), obj.code) } if err := s.keeper.SetAccount(s.ctx, obj.Address(), obj.account); err != nil { - return errorsmod.Wrap(err, "failed to set account") + return s.ctx, errorsmod.Wrap(err, "failed to set account") } for _, key := range obj.dirtyStorage.SortedKeys() { value := obj.dirtyStorage[key] @@ -474,5 +489,5 @@ func (s *StateDB) Commit() error { } } } - return nil + return s.ctx, nil } From 76d65bfe91748d64eced901306862ce5c5a6133c Mon Sep 17 00:00:00 2001 From: mmsqe Date: Wed, 11 Jan 2023 09:31:05 +0800 Subject: [PATCH 02/41] fix to BenchmarkMessageCall --- go.mod | 4 ++-- go.sum | 8 ++++---- gomod2nix.toml | 10 +++++----- x/evm/keeper/grpc_query_test.go | 18 ++++++++++++------ x/evm/keeper/statedb_test.go | 6 ++++-- x/evm/keeper/utils_test.go | 4 ++-- 6 files changed, 29 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index d81d6b6381..00d6ec53b4 100644 --- a/go.mod +++ b/go.mod @@ -165,7 +165,7 @@ require ( github.com/tendermint/btcd v0.1.1 // indirect github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect github.com/tendermint/go-amino v0.16.0 // indirect - github.com/tidwall/btree v1.5.0 // indirect + github.com/tidwall/btree v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect github.com/ulikunitz/xz v0.5.8 // indirect @@ -193,7 +193,7 @@ require ( replace ( // use cosmos keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 - github.com/cosmos/cosmos-sdk => github.com/yihuang/cosmos-sdk v0.43.0-beta1.0.20230110141630-be56dc1a6a02 + github.com/cosmos/cosmos-sdk => github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49 // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.0 diff --git a/go.sum b/go.sum index ec745574be..295a43d5d0 100644 --- a/go.sum +++ b/go.sum @@ -746,6 +746,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49 h1:xyzaLfcVtXPuJcESBsl8FGFhgth+2YNHWyzo9a8lPnU= +github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49/go.mod h1:rL1mzp5+40g7djg1S1KMPoeg963PUARW9L2hC1pQqOI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -985,8 +987,8 @@ github.com/tendermint/tendermint v0.34.24 h1:879MKKJWYYPJEMMKME+DWUTY4V9f/FBpnZD github.com/tendermint/tendermint v0.34.24/go.mod h1:rXVrl4OYzmIa1I91av3iLv2HS0fGSiucyW9J4aMTpKI= github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= -github.com/tidwall/btree v1.5.0 h1:iV0yVY/frd7r6qGBXfEYs7DH0gTDgrKTrDjS7xt/IyQ= -github.com/tidwall/btree v1.5.0/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE= +github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= +github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -1030,8 +1032,6 @@ github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6Ut github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= -github.com/yihuang/cosmos-sdk v0.43.0-beta1.0.20230110141630-be56dc1a6a02 h1:xA0FbP9k+AiQi453ugj1zQk11jN8zDR3+O/ZAvefqPk= -github.com/yihuang/cosmos-sdk v0.43.0-beta1.0.20230110141630-be56dc1a6a02/go.mod h1:q5j3014GTxF1SPLoIR5auNOwnw0a4sflym3YsO8lWhs= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/gomod2nix.toml b/gomod2nix.toml index 85b696b47d..d10ee398ca 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -96,9 +96,9 @@ schema = 3 version = "v1.0.0-beta.1" hash = "sha256-oATkuj+fM5eBn+ywO+w/tL0AFSIEkx0J3Yz+VhVe0QA=" [mod."github.com/cosmos/cosmos-sdk"] - version = "v0.43.0-beta1.0.20230110141630-be56dc1a6a02" - hash = "sha256-xPsC7XYq7Cye1q9lKzi3A6v2+1Uv3JbKW+It08T5/iY=" - replaced = "github.com/yihuang/cosmos-sdk" + version = "v0.46.6-0.20230110154545-70f48768ca49" + hash = "sha256-9XwV1EAwL4VKA1anZ7fEoC8hr8a31uvTn3syRwuTA7A=" + replaced = "github.com/mmsqe/cosmos-sdk" [mod."github.com/cosmos/go-bip39"] version = "v1.0.0" hash = "sha256-Qm2aC2vaS8tjtMUbHmlBSagOSqbduEEDwc51qvQaBmA=" @@ -464,8 +464,8 @@ schema = 3 version = "v0.6.7" hash = "sha256-hl/3RrBrpkk2zA6dmrNlIYKs1/GfqegSscDSkA5Pjlo=" [mod."github.com/tidwall/btree"] - version = "v1.5.0" - hash = "sha256-iWll4/+ADLVse3VAHxXYLprILugX/+3u0ZIk0YlLv/Q=" + version = "v1.6.0" + hash = "sha256-H4S46Yk3tVfOtrEhVWUrF4S1yWYmzU43W80HlzS9rcY=" [mod."github.com/tklauser/go-sysconf"] version = "v0.3.10" hash = "sha256-Zf2NsgM9+HeM949vCce4HQtSbfUiFpeiQ716yKcFyx4=" diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index a56b78ff48..3918501ed2 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -272,7 +272,8 @@ func (suite *KeeperTestSuite) TestQueryStorage() { vmdb := suite.StateDB() tc.malleate(vmdb) - suite.Require().NoError(vmdb.Commit()) + _, err := vmdb.Commit() + suite.Require().NoError(err) ctx := sdk.WrapSDKContext(suite.ctx) res, err := suite.queryClient.Storage(ctx, req) @@ -331,7 +332,8 @@ func (suite *KeeperTestSuite) TestQueryCode() { vmdb := suite.StateDB() tc.malleate(vmdb) - suite.Require().NoError(vmdb.Commit()) + _, err := vmdb.Commit() + suite.Require().NoError(err) ctx := sdk.WrapSDKContext(suite.ctx) res, err := suite.queryClient.Code(ctx, req) @@ -394,7 +396,8 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() { vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()), txHash, txIndex, logIndex)) tc.malleate(vmdb) - suite.Require().NoError(vmdb.Commit()) + _, err := vmdb.Commit() + suite.Require().NoError(err) logs := vmdb.Logs() suite.Require().Equal(expLogs, types.NewLogsFromEth(logs)) @@ -837,7 +840,8 @@ func (suite *KeeperTestSuite) TestTraceTx() { // increase nonce to avoid address collision vmdb := suite.StateDB() vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1) - suite.Require().NoError(vmdb.Commit()) + _, err := vmdb.Commit() + suite.Require().NoError(err) contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) suite.Commit() @@ -896,7 +900,8 @@ func (suite *KeeperTestSuite) TestTraceTx() { // increase nonce to avoid address collision vmdb := suite.StateDB() vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1) - suite.Require().NoError(vmdb.Commit()) + _, err := vmdb.Commit() + suite.Require().NoError(err) chainID := suite.app.EvmKeeper.ChainID() nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address) @@ -1057,7 +1062,8 @@ func (suite *KeeperTestSuite) TestTraceBlock() { // increase nonce to avoid address collision vmdb := suite.StateDB() vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1) - suite.Require().NoError(vmdb.Commit()) + _, err := vmdb.Commit() + suite.Require().NoError(err) contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) suite.Commit() diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index b12d0dd460..ca8af5bfbe 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -451,7 +451,8 @@ func (suite *KeeperTestSuite) TestSuicide() { db.SetState(suite.address, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), common.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) } - suite.Require().NoError(db.Commit()) + _, err := db.Commit() + suite.Require().NoError(err) db = suite.StateDB() // Generate 2nd address @@ -474,7 +475,8 @@ func (suite *KeeperTestSuite) TestSuicide() { suite.Require().Equal(true, db.HasSuicided(suite.address)) // Commit state - suite.Require().NoError(db.Commit()) + _, err = db.Commit() + suite.Require().NoError(err) db = suite.StateDB() // Check code is deleted diff --git a/x/evm/keeper/utils_test.go b/x/evm/keeper/utils_test.go index f14e119f29..47207d842f 100644 --- a/x/evm/keeper/utils_test.go +++ b/x/evm/keeper/utils_test.go @@ -207,7 +207,7 @@ func (suite *KeeperTestSuite) TestCheckSenderBalance() { vmdb.AddBalance(suite.address, hundredInt.BigInt()) balance := vmdb.GetBalance(suite.address) suite.Require().Equal(balance, hundredInt.BigInt()) - err := vmdb.Commit() + _, err := vmdb.Commit() suite.Require().NoError(err, "Unexpected error while committing to vmdb: %d", err) for i, tc := range testCases { @@ -467,7 +467,7 @@ func (suite *KeeperTestSuite) TestVerifyFeeAndDeductTxCostsFromUserBalance() { balance := vmdb.GetBalance(suite.address) suite.Require().Equal(balance, hundredInt.BigInt()) } - err := vmdb.Commit() + _, err := vmdb.Commit() suite.Require().NoError(err, "Unexpected error while committing to vmdb: %d", err) tx := evmtypes.NewTx(zeroInt.BigInt(), 1, &suite.address, amount, tc.gasLimit, gasPrice, gasFeeCap, gasTipCap, nil, tc.accessList) From 869b33fd278b867d195f0a9e2b7fe8dbf876f0ca Mon Sep 17 00:00:00 2001 From: HuangYi Date: Thu, 16 Jun 2022 17:55:00 +0800 Subject: [PATCH 03/41] Bank precompiled contract temp temp fix build temp fix build support mint method mint and query balance --- app/ante/eth.go | 15 +- app/ante/interfaces.go | 5 +- app/app.go | 10 +- go.mod | 1 + go.sum | 65 ++---- gomod2nix.toml | 5 +- .../hardhat/contracts/TestBank.sol | 23 ++ tests/integration_tests/test_precompiles.py | 30 +++ tests/integration_tests/utils.py | 1 + x/evm/keeper/keeper.go | 10 + x/evm/keeper/precompiles/bank.go | 217 ++++++++++++++++++ x/evm/keeper/precompiles/types.go | 8 + x/evm/keeper/state_transition.go | 44 +++- x/evm/statedb/precompiles.go | 14 ++ x/evm/statedb/statedb.go | 24 +- x/evm/statedb/types.go | 12 + x/evm/vm/geth/geth.go | 6 +- x/evm/vm/interface.go | 2 +- 18 files changed, 415 insertions(+), 77 deletions(-) create mode 100644 tests/integration_tests/hardhat/contracts/TestBank.sol create mode 100644 tests/integration_tests/test_precompiles.py create mode 100644 x/evm/keeper/precompiles/bank.go create mode 100644 x/evm/keeper/precompiles/types.go create mode 100644 x/evm/statedb/precompiles.go create mode 100644 x/evm/statedb/types.go diff --git a/app/ante/eth.go b/app/ante/eth.go index 2402167be7..8615476cd5 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -31,6 +31,7 @@ import ( evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" ) @@ -294,20 +295,10 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate } } - // NOTE: pass in an empty coinbase address and nil tracer as we don't need them for the check below - cfg := &statedb.EVMConfig{ - ChainConfig: ethCfg, - Params: params, - CoinBase: common.Address{}, - BaseFee: baseFee, - } - - stateDB := statedb.New(ctx, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) - evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB) - + stateDB := ctd.evmKeeper.StateDB(ctx, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes())), nil) // check that caller has enough balance to cover asset transfer for **topmost** call // NOTE: here the gas consumed is from the context with the infinite gas meter - if coreMsg.Value().Sign() > 0 && !evm.Context().CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) { + if coreMsg.Value().Sign() > 0 && !core.CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) { return ctx, errorsmod.Wrapf( errortypes.ErrInsufficientFunds, "failed to transfer %s from address %s using the EVM block context transfer function", diff --git a/app/ante/interfaces.go b/app/ante/interfaces.go index e48e0a50a4..4947e3861e 100644 --- a/app/ante/interfaces.go +++ b/app/ante/interfaces.go @@ -22,13 +22,10 @@ import ( tx "github.com/cosmos/cosmos-sdk/types/tx" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" "github.com/evmos/ethermint/x/evm/statedb" evmtypes "github.com/evmos/ethermint/x/evm/types" - evm "github.com/evmos/ethermint/x/evm/vm" feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" ) @@ -44,12 +41,12 @@ type EVMKeeper interface { statedb.Keeper DynamicFeeEVMKeeper - NewEVM(ctx sdk.Context, msg core.Message, cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) evm.EVM DeductTxCostsFromUserBalance(ctx sdk.Context, fees sdk.Coins, from common.Address) error GetBalance(ctx sdk.Context, addr common.Address) *big.Int ResetTransientGasUsed(ctx sdk.Context) GetTxIndexTransient(ctx sdk.Context) uint64 GetParams(ctx sdk.Context) evmtypes.Params + StateDB(ctx sdk.Context, txConfig statedb.TxConfig, extStates []statedb.ExtState) *statedb.StateDB } type protoTxProvider interface { diff --git a/app/app.go b/app/app.go index 7e5b3e8982..4fa452930a 100644 --- a/app/app.go +++ b/app/app.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "io" + "math/big" "net/http" "os" "path/filepath" @@ -124,6 +125,8 @@ import ( ethermint "github.com/evmos/ethermint/types" "github.com/evmos/ethermint/x/evm" evmkeeper "github.com/evmos/ethermint/x/evm/keeper" + "github.com/evmos/ethermint/x/evm/keeper/precompiles" + "github.com/evmos/ethermint/x/evm/statedb" evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/evmos/ethermint/x/evm/vm/geth" "github.com/evmos/ethermint/x/feemarket" @@ -131,6 +134,7 @@ import ( feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" // Force-load the tracer engines to trigger registration due to Go-Ethereum v1.10.15 changes + "github.com/ethereum/go-ethereum/common" _ "github.com/ethereum/go-ethereum/eth/tracers/js" _ "github.com/ethereum/go-ethereum/eth/tracers/native" ) @@ -417,13 +421,15 @@ func NewEthermintApp( appCodec, authtypes.NewModuleAddress(govtypes.ModuleName), keys[feemarkettypes.StoreKey], tkeys[feemarkettypes.TransientKey], feeMarketSs, ) - + contracts := map[common.Address]statedb.PrecompiledContractCreator{ + common.BigToAddress(big.NewInt(100)): precompiles.NewBankContractCreator(app.BankKeeper), + } // Set authority to x/gov module account to only expect the module account to update params evmSs := app.GetSubspace(evmtypes.ModuleName) app.EvmKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.FeeMarketKeeper, - nil, geth.NewEVM, tracer, evmSs, + nil, geth.NewEVM, tracer, evmSs, contracts, ) // Create IBC Keeper diff --git a/go.mod b/go.mod index 00d6ec53b4..0c021972e3 100644 --- a/go.mod +++ b/go.mod @@ -194,6 +194,7 @@ replace ( // use cosmos keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 github.com/cosmos/cosmos-sdk => github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49 + github.com/ethereum/go-ethereum => github.com/mmsqe/go-ethereum v1.10.26-0.20230127080103-3a00e53a24bb // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.0 diff --git a/go.sum b/go.sum index 295a43d5d0..7457677741 100644 --- a/go.sum +++ b/go.sum @@ -59,24 +59,12 @@ filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmG filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA= -github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= -github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= -github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= @@ -156,10 +144,9 @@ github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd v0.22.2 h1:vBZ+lGGd1XubpOWO67ITJpAEsICWhA0YzqkcpkgNBfo= github.com/btcsuite/btcd v0.22.2/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= -github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -263,7 +250,6 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= @@ -287,16 +273,13 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= -github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf h1:Yt+4K30SdjOkRoRRm3vYNQgR+/ZIy0RmeUDZo7Y8zeQ= github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= @@ -319,10 +302,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.4/go.mod h1:nEE0TP5MtxGzOMd7egIrbPJMQBnhVU3ELNxhBglIzhg= -github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= -github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= @@ -330,6 +309,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -341,6 +321,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= @@ -384,7 +365,6 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -444,7 +424,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -492,7 +471,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -516,7 +494,6 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= @@ -583,8 +560,6 @@ github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= -github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= @@ -607,7 +582,6 @@ github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19y github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= @@ -641,7 +615,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= @@ -669,6 +642,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= @@ -693,7 +667,6 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -701,11 +674,8 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -748,6 +718,8 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49 h1:xyzaLfcVtXPuJcESBsl8FGFhgth+2YNHWyzo9a8lPnU= github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49/go.mod h1:rL1mzp5+40g7djg1S1KMPoeg963PUARW9L2hC1pQqOI= +github.com/mmsqe/go-ethereum v1.10.26-0.20230127080103-3a00e53a24bb h1:WXZVxmbB/oIpjVMzdhsxhuvXwXiJvFlUqbpQ+Na/0SI= +github.com/mmsqe/go-ethereum v1.10.26-0.20230127080103-3a00e53a24bb/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -784,6 +756,7 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= @@ -969,12 +942,13 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= @@ -1021,6 +995,7 @@ github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= +github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -1031,12 +1006,14 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.1 h1:Pip65OOl4iJ84WTpA4BKChvOufMhhbxED3BaihoZN4c= @@ -1085,6 +1062,7 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= @@ -1102,6 +1080,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -1129,12 +1108,13 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1186,6 +1166,7 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1289,7 +1270,6 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1297,6 +1277,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= @@ -1349,6 +1330,7 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1383,11 +1365,13 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -1529,11 +1513,9 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1549,7 +1531,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/gomod2nix.toml b/gomod2nix.toml index d10ee398ca..8ec2bb911b 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -160,8 +160,9 @@ schema = 3 version = "v1.0.0" hash = "sha256-k1DYvCqO3BKNcGEve/nMW0RxzMkK2tGfXbUbycqcVSo=" [mod."github.com/ethereum/go-ethereum"] - version = "v1.10.26" - hash = "sha256-gkMEwJ4rOgn12amD4QpZ4th/10uyTTeoFmpseuKDQPs=" + version = "v1.10.26-0.20230127080103-3a00e53a24bb" + hash = "sha256-UCj5SaNjB/4Q9nI3DSx/HLYr22QHouzYyQ0Kw3KEXz0=" + replaced = "github.com/mmsqe/go-ethereum" [mod."github.com/felixge/httpsnoop"] version = "v1.0.1" hash = "sha256-TNXnnC/ZGNY9lInAcES1cBGqIdEljKuh5LH/khVFjVk=" diff --git a/tests/integration_tests/hardhat/contracts/TestBank.sol b/tests/integration_tests/hardhat/contracts/TestBank.sol new file mode 100644 index 0000000000..20b3eff9e7 --- /dev/null +++ b/tests/integration_tests/hardhat/contracts/TestBank.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >0.6.6; + +contract TestBank { + address constant bankContract = 0x0000000000000000000000000000000000000064; + function nativeMint(uint amount) public { + (bool result, bytes memory _data) = bankContract.call(abi.encodeWithSignature( + "mint(address,uint256)", msg.sender, amount + )); + require(result, "native call"); + } + function nativeBalanceOf(address addr) public returns (uint) { + (bool result, bytes memory data) = bankContract.call(abi.encodeWithSignature( + "balanceOf(address,address)", address(this), addr + )); + require(result, "native call"); + return abi.decode(data, (uint)); + } + function nativeMintRevert(uint amount) public { + nativeMint(amount); + revert("test"); + } +} diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py new file mode 100644 index 0000000000..93a7bac946 --- /dev/null +++ b/tests/integration_tests/test_precompiles.py @@ -0,0 +1,30 @@ +from .utils import ADDRS, CONTRACTS, deploy_contract, eth_to_bech32, send_transaction + + +def test_precompiles(ethermint): + w3 = ethermint.w3 + addr = ADDRS["validator"] + amount = 100 + contract, _ = deploy_contract(w3, CONTRACTS["TestBank"]) + tx = contract.functions.nativeMint(amount).build_transaction({"from": addr}) + receipt = send_transaction(w3, tx) + assert receipt.status == 1, "expect success" + + # query balance through contract + assert contract.caller.nativeBalanceOf(addr) == amount + # query balance through cosmos rpc + cli = ethermint.cosmos_cli() + assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount + + # test exception revert + tx = contract.functions.nativeMintRevert(amount).build_transaction( + {"from": addr, "gas": 210000} + ) + receipt = send_transaction(w3, tx) + assert receipt.status == 0, "expect failure" + + # check balance don't change + assert contract.caller.nativeBalanceOf(addr) == amount + # query balance through cosmos rpc + cli = ethermint.cosmos_cli() + assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount diff --git a/tests/integration_tests/utils.py b/tests/integration_tests/utils.py index b277beeaa3..23fa58609a 100644 --- a/tests/integration_tests/utils.py +++ b/tests/integration_tests/utils.py @@ -32,6 +32,7 @@ "TestChainID": "ChainID.sol", "Mars": "Mars.sol", "StateContract": "StateContract.sol", + "TestBank": "TestBank.sol", } diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 2d3e90a175..31019f2cf8 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -78,6 +78,9 @@ type Keeper struct { evmConstructor evm.Constructor // Legacy subspace ss paramstypes.Subspace + + // Stateful EVM precompiled contracts + precompiles map[common.Address]statedb.PrecompiledContractCreator } // NewKeeper generates new evm module keeper @@ -93,6 +96,7 @@ func NewKeeper( evmConstructor evm.Constructor, tracer string, ss paramstypes.Subspace, + precompiles map[common.Address]statedb.PrecompiledContractCreator, ) *Keeper { // ensure evm module account is set if addr := ak.GetModuleAddress(types.ModuleName); addr == nil { @@ -118,6 +122,7 @@ func NewKeeper( evmConstructor: evmConstructor, tracer: tracer, ss: ss, + precompiles: precompiles, } } @@ -393,3 +398,8 @@ func (k Keeper) AddTransientGasUsed(ctx sdk.Context, gasUsed uint64) (uint64, er k.SetTransientGasUsed(ctx, result) return result, nil } + +// StateDB creates a StateDB instance +func (k Keeper) StateDB(ctx sdk.Context, txConfig statedb.TxConfig, extStates []statedb.ExtState) *statedb.StateDB { + return statedb.New(ctx, &k, txConfig, extStates) +} diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go new file mode 100644 index 0000000000..73359b3a98 --- /dev/null +++ b/x/evm/keeper/precompiles/bank.go @@ -0,0 +1,217 @@ +package precompiles + +import ( + "bytes" + "errors" + "math/big" + "sort" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/evmos/ethermint/x/evm/statedb" + "github.com/evmos/ethermint/x/evm/types" +) + +const EVMDenomPrefix = "evm/" + +var ( + MintMethod abi.Method + BalanceOfMethod abi.Method + + _ statedb.StatefulPrecompiledContract = (*BankContract)(nil) + _ statedb.JournalEntry = bankMintChange{} +) + +func init() { + addressType, _ := abi.NewType("address", "", nil) + uint256Type, _ := abi.NewType("uint256", "", nil) + MintMethod = abi.NewMethod( + "mint", "mint", abi.Function, "", false, false, abi.Arguments{abi.Argument{ + Name: "recipient", + Type: addressType, + }, abi.Argument{ + Name: "amount", + Type: uint256Type, + }}, + nil, + ) + BalanceOfMethod = abi.NewMethod( + "balanceOf", "balanceOf", abi.Function, "", false, false, abi.Arguments{abi.Argument{ + Name: "token", + Type: addressType, + }, abi.Argument{ + Name: "address", + Type: addressType, + }}, + abi.Arguments{abi.Argument{ + Name: "amount", + Type: uint256Type, + }}, + ) +} + +func EVMDenom(token common.Address) string { + return EVMDenomPrefix + token.Hex() +} + +type Balance struct { + OriginAmount *big.Int + DirtyAmount *big.Int +} + +func (b Balance) Changed() *big.Int { + return new(big.Int).Sub(b.DirtyAmount, b.OriginAmount) +} + +type BankContract struct { + ctx sdk.Context + bankKeeper types.BankKeeper + balances map[common.Address]map[common.Address]*Balance +} + +// NewBankContractCreator creates the precompiled contract to manage native tokens +func NewBankContractCreator(bankKeeper types.BankKeeper) statedb.PrecompiledContractCreator { + return func(ctx sdk.Context) statedb.StatefulPrecompiledContract { + return &BankContract{ + ctx: ctx, + bankKeeper: bankKeeper, + balances: make(map[common.Address]map[common.Address]*Balance), + } + } +} + +// RequiredGas calculates the contract gas use +func (bc *BankContract) RequiredGas(input []byte) uint64 { + // TODO estimate required gas + return 0 +} + +func (bc *BankContract) Run(evm *vm.EVM, input []byte, caller common.Address, value *big.Int, readonly bool) ([]byte, error) { + stateDB, ok := evm.StateDB.(ExtStateDB) + if !ok { + return nil, errors.New("not run in ethermint") + } + + // parse input + methodID := input[:4] + if bytes.Equal(methodID, MintMethod.ID) { + if readonly { + return nil, errors.New("the method is not readonly") + } + args, err := MintMethod.Inputs.Unpack(input[4:]) + if err != nil { + return nil, errors.New("fail to unpack input arguments") + } + recipient := args[0].(common.Address) + amount := args[1].(*big.Int) + if amount.Sign() <= 0 { + return nil, errors.New("invalid amount") + } + + if _, ok := bc.balances[caller]; !ok { + bc.balances[caller] = make(map[common.Address]*Balance) + } + balances := bc.balances[caller] + if balance, ok := balances[recipient]; ok { + balance.DirtyAmount = new(big.Int).Add(balance.DirtyAmount, amount) + } else { + // query original amount + addr := sdk.AccAddress(recipient.Bytes()) + originAmount := bc.bankKeeper.GetBalance(bc.ctx, addr, EVMDenom(caller)).Amount.BigInt() + dirtyAmount := new(big.Int).Add(originAmount, amount) + balances[recipient] = &Balance{ + OriginAmount: originAmount, + DirtyAmount: dirtyAmount, + } + } + stateDB.AppendJournalEntry(bankMintChange{bc: bc, caller: caller, recipient: recipient, amount: amount}) + } else if bytes.Equal(methodID, BalanceOfMethod.ID) { + args, err := BalanceOfMethod.Inputs.Unpack(input[4:]) + if err != nil { + return nil, errors.New("fail to unpack input arguments") + } + token := args[0].(common.Address) + addr := args[1].(common.Address) + if balances, ok := bc.balances[token]; ok { + if balance, ok := balances[addr]; ok { + return BalanceOfMethod.Outputs.Pack(balance.DirtyAmount) + } + } + // query from storage + amount := bc.bankKeeper.GetBalance(bc.ctx, sdk.AccAddress(addr.Bytes()), EVMDenom(token)).Amount.BigInt() + return BalanceOfMethod.Outputs.Pack(amount) + } else { + return nil, errors.New("unknown method") + } + return nil, nil +} + +func (bc *BankContract) Commit(ctx sdk.Context) error { + // sorted iteration + sortedContracts := make([]common.Address, len(bc.balances)) + i := 0 + for contract := range bc.balances { + sortedContracts[i] = contract + i++ + } + sort.Slice(sortedContracts, func(i, j int) bool { + return bytes.Compare(sortedContracts[i].Bytes(), sortedContracts[j].Bytes()) < 0 + }) + for _, contract := range sortedContracts { + denom := EVMDenom(contract) + balances := bc.balances[contract] + sortedRecipients := make([]common.Address, len(balances)) + i = 0 + for recipient := range balances { + sortedRecipients[i] = recipient + i++ + } + sort.Slice(sortedRecipients, func(i, j int) bool { + return bytes.Compare(sortedRecipients[i].Bytes(), sortedRecipients[j].Bytes()) < 0 + }) + for _, recipient := range sortedRecipients { + cosmosAddr := sdk.AccAddress(recipient.Bytes()) + changed := balances[recipient].Changed() + switch changed.Sign() { + case 1: + amt := sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(changed))) + if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, amt); err != nil { + return sdkerrors.Wrap(err, "fail to mint coins in precompiled contract") + } + if err := bc.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, cosmosAddr, amt); err != nil { + return sdkerrors.Wrap(err, "fail to send mint coins to account") + } + case -1: + amt := sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(new(big.Int).Neg(changed)))) + if err := bc.bankKeeper.SendCoinsFromAccountToModule(ctx, cosmosAddr, types.ModuleName, amt); err != nil { + return sdkerrors.Wrap(err, "fail to send burn coins to module") + } + if err := bc.bankKeeper.BurnCoins(ctx, types.ModuleName, amt); err != nil { + return sdkerrors.Wrap(err, "fail to burn coins in precompiled contract") + } + + } + } + } + return nil +} + +type bankMintChange struct { + bc *BankContract + caller common.Address + recipient common.Address + amount *big.Int +} + +func (ch bankMintChange) Revert(*statedb.StateDB) { + balance := ch.bc.balances[ch.caller][ch.recipient] + balance.DirtyAmount = new(big.Int).Sub(balance.DirtyAmount, ch.amount) +} + +func (ch bankMintChange) Dirtied() *common.Address { + return nil +} diff --git a/x/evm/keeper/precompiles/types.go b/x/evm/keeper/precompiles/types.go new file mode 100644 index 0000000000..1a69810727 --- /dev/null +++ b/x/evm/keeper/precompiles/types.go @@ -0,0 +1,8 @@ +package precompiles + +import "github.com/evmos/ethermint/x/evm/statedb" + +// ExtStateDB defines extra methods of statedb to support stateful precompiled contracts +type ExtStateDB interface { + AppendJournalEntry(statedb.JournalEntry) +} diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 6b98d3c97e..c51a202250 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -16,7 +16,9 @@ package keeper import ( + "bytes" "math/big" + "sort" tmtypes "github.com/tendermint/tendermint/types" @@ -26,7 +28,6 @@ import ( ethermint "github.com/evmos/ethermint/types" "github.com/evmos/ethermint/x/evm/statedb" "github.com/evmos/ethermint/x/evm/types" - evm "github.com/evmos/ethermint/x/evm/vm" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -51,7 +52,8 @@ func (k *Keeper) NewEVM( cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB, -) evm.EVM { + contracts map[common.Address]statedb.StatefulPrecompiledContract, +) *vm.EVM { blockCtx := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, @@ -70,7 +72,15 @@ func (k *Keeper) NewEVM( tracer = k.Tracer(ctx, msg, cfg.ChainConfig) } vmConfig := k.VMConfig(ctx, msg, cfg, tracer) - return k.evmConstructor(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig, k.customPrecompiles) + rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil) + precompiles := make(map[common.Address]vm.PrecompiledContract) + for addr, c := range vm.ActivePrecompiledContracts(rules) { + precompiles[addr] = c + } + for addr, c := range contracts { + precompiles[addr] = c + } + return vm.NewEVMWithPrecompiles(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig, precompiles) } // GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases: @@ -329,13 +339,31 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, return nil, errorsmod.Wrap(types.ErrCallDisabled, "failed to call contract") } - stateDB := statedb.New(ctx, k, txConfig) - evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB) + // construct precompiles + contracts := make(map[common.Address]statedb.StatefulPrecompiledContract, len(k.precompiles)) + sortedContracts := make([]common.Address, 0, len(k.precompiles)) + for addr, creator := range k.precompiles { + c := creator(ctx) + contracts[addr] = c + sortedContracts = append(sortedContracts, addr) + } + + sort.Slice(sortedContracts, func(i, j int) bool { + return bytes.Compare(sortedContracts[i].Bytes(), sortedContracts[j].Bytes()) < 0 + }) + + extStates := make([]statedb.ExtState, len(sortedContracts)) + for i, addr := range sortedContracts { + extStates[i] = contracts[addr] + } + + stateDB := k.StateDB(ctx, txConfig, extStates) + evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB, contracts) leftoverGas := msg.Gas() // Allow the tracer captures the tx level events, mainly the gas consumption. - vmCfg := evm.Config() + vmCfg := evm.Config if vmCfg.Debug { vmCfg.Tracer.CaptureTxStart(leftoverGas) defer func() { @@ -345,7 +373,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, sender := vm.AccountRef(msg.From()) contractCreation := msg.To() == nil - isLondon := cfg.ChainConfig.IsLondon(evm.Context().BlockNumber) + isLondon := cfg.ChainConfig.IsLondon(evm.Context.BlockNumber) intrinsicGas, err := k.GetEthIntrinsicGas(ctx, msg, cfg.ChainConfig, contractCreation) if err != nil { @@ -363,7 +391,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, // access list preparation is moved from ante handler to here, because it's needed when `ApplyMessage` is called // under contexts where ante handlers are not run, for example `eth_call` and `eth_estimateGas`. if rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil); rules.IsBerlin { - stateDB.PrepareAccessList(msg.From(), msg.To(), evm.ActivePrecompiles(rules), msg.AccessList()) + stateDB.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) } if contractCreation { diff --git a/x/evm/statedb/precompiles.go b/x/evm/statedb/precompiles.go new file mode 100644 index 0000000000..512836d500 --- /dev/null +++ b/x/evm/statedb/precompiles.go @@ -0,0 +1,14 @@ +package statedb + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +// StatefulPrecompiledContract is a stateful precompiled contract in evm +type StatefulPrecompiledContract interface { + vm.PrecompiledContract + ExtState +} + +type PrecompiledContractCreator func(sdk.Context) StatefulPrecompiledContract diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 03e71095e6..eafaf5990c 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -65,19 +65,25 @@ type StateDB struct { // Per-transaction access list accessList *accessList + + // extended states + extStates []ExtState } // New creates a new state from a given trie. -func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { - return &StateDB{ +func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig, extStates []ExtState) *StateDB { + statedb := &StateDB{ keeper: keeper, ctx: ctx, stateObjects: make(map[common.Address]*stateObject), journal: newJournal(), accessList: newAccessList(), - txConfig: txConfig, + txConfig: txConfig, + extStates: extStates, } + + return statedb } // Keeper returns the underlying `Keeper` @@ -85,6 +91,12 @@ func (s *StateDB) Keeper() Keeper { return s.keeper } +// AppendJournalEntry allow external module to append journal entry, +// to support snapshot revert for external states. +func (s *StateDB) AppendJournalEntry(entry JournalEntry) { + s.journal.append(entry) +} + // AddLog adds a log, called by evm. func (s *StateDB) AddLog(log *ethtypes.Log) { s.journal.append(addLogChange{}) @@ -489,5 +501,11 @@ func (s *StateDB) Commit() (sdk.Context, error) { } } } + // commit the extended states + for _, ext := range s.extStates { + if err := ext.Commit(s.ctx); err != nil { + return s.ctx, err + } + } return s.ctx, nil } diff --git a/x/evm/statedb/types.go b/x/evm/statedb/types.go new file mode 100644 index 0000000000..a580972647 --- /dev/null +++ b/x/evm/statedb/types.go @@ -0,0 +1,12 @@ +package statedb + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// ExtState manage some extended states which needs to be committed together with StateDB. +// mainly for the stateful precompiled contracts. +type ExtState interface { + // write the dirty states to cosmos-sdk storage + Commit(sdk.Context) error +} diff --git a/x/evm/vm/geth/geth.go b/x/evm/vm/geth/geth.go index f248e6879b..c188d5d15b 100644 --- a/x/evm/vm/geth/geth.go +++ b/x/evm/vm/geth/geth.go @@ -83,12 +83,12 @@ func (EVM) ActivePrecompiles(rules params.Rules) []common.Address { // RunPrecompiledContract runs a stateless precompiled contract and ignores the address and // value arguments. It uses the RunPrecompiledContract function from the geth vm package -func (EVM) RunPrecompiledContract( +func (e EVM) RunPrecompiledContract( p evm.StatefulPrecompiledContract, _ common.Address, // address arg is unused input []byte, suppliedGas uint64, - _ *big.Int, // value arg is unused + caller common.Address, value *big.Int, readonly bool, ) (ret []byte, remainingGas uint64, err error) { - return vm.RunPrecompiledContract(p, input, suppliedGas) + return vm.RunPrecompiledContract(p, e.EVM, input, suppliedGas, caller, value, readonly) } diff --git a/x/evm/vm/interface.go b/x/evm/vm/interface.go index f1a6f4f707..41dcd61ded 100644 --- a/x/evm/vm/interface.go +++ b/x/evm/vm/interface.go @@ -64,7 +64,7 @@ type EVM interface { addr common.Address, input []byte, suppliedGas uint64, - value *big.Int) ( + caller common.Address, value *big.Int, readonly bool) ( ret []byte, remainingGas uint64, err error, ) } From b6007dc4970d6d96f8c930c0bb672f6ac4527155 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 30 Jan 2023 18:35:57 +0800 Subject: [PATCH 04/41] replace extStates commit with executeNativeAction --- app/ante/eth.go | 2 +- app/ante/interfaces.go | 2 +- x/evm/keeper/keeper.go | 4 +- x/evm/keeper/precompiles/bank.go | 120 +++++------------------------- x/evm/keeper/precompiles/types.go | 4 +- x/evm/keeper/state_transition.go | 18 +---- x/evm/statedb/precompiles.go | 1 - x/evm/statedb/statedb.go | 16 +--- 8 files changed, 28 insertions(+), 139 deletions(-) diff --git a/app/ante/eth.go b/app/ante/eth.go index 8615476cd5..0c0a885b2f 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -295,7 +295,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate } } - stateDB := ctd.evmKeeper.StateDB(ctx, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes())), nil) + stateDB := ctd.evmKeeper.StateDB(ctx, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) // check that caller has enough balance to cover asset transfer for **topmost** call // NOTE: here the gas consumed is from the context with the infinite gas meter if coreMsg.Value().Sign() > 0 && !core.CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) { diff --git a/app/ante/interfaces.go b/app/ante/interfaces.go index 4947e3861e..8dcac7fa9c 100644 --- a/app/ante/interfaces.go +++ b/app/ante/interfaces.go @@ -46,7 +46,7 @@ type EVMKeeper interface { ResetTransientGasUsed(ctx sdk.Context) GetTxIndexTransient(ctx sdk.Context) uint64 GetParams(ctx sdk.Context) evmtypes.Params - StateDB(ctx sdk.Context, txConfig statedb.TxConfig, extStates []statedb.ExtState) *statedb.StateDB + StateDB(ctx sdk.Context, txConfig statedb.TxConfig) *statedb.StateDB } type protoTxProvider interface { diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 31019f2cf8..ddc6e4b07f 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -400,6 +400,6 @@ func (k Keeper) AddTransientGasUsed(ctx sdk.Context, gasUsed uint64) (uint64, er } // StateDB creates a StateDB instance -func (k Keeper) StateDB(ctx sdk.Context, txConfig statedb.TxConfig, extStates []statedb.ExtState) *statedb.StateDB { - return statedb.New(ctx, &k, txConfig, extStates) +func (k Keeper) StateDB(ctx sdk.Context, txConfig statedb.TxConfig) *statedb.StateDB { + return statedb.New(ctx, &k, txConfig) } diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index 73359b3a98..c8b8ca545d 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -1,11 +1,10 @@ package precompiles import ( - "bytes" "errors" "math/big" - "sort" + sdkmath "cosmossdk.io/math" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" @@ -23,7 +22,6 @@ var ( BalanceOfMethod abi.Method _ statedb.StatefulPrecompiledContract = (*BankContract)(nil) - _ statedb.JournalEntry = bankMintChange{} ) func init() { @@ -58,19 +56,9 @@ func EVMDenom(token common.Address) string { return EVMDenomPrefix + token.Hex() } -type Balance struct { - OriginAmount *big.Int - DirtyAmount *big.Int -} - -func (b Balance) Changed() *big.Int { - return new(big.Int).Sub(b.DirtyAmount, b.OriginAmount) -} - type BankContract struct { ctx sdk.Context bankKeeper types.BankKeeper - balances map[common.Address]map[common.Address]*Balance } // NewBankContractCreator creates the precompiled contract to manage native tokens @@ -79,7 +67,6 @@ func NewBankContractCreator(bankKeeper types.BankKeeper) statedb.PrecompiledCont return &BankContract{ ctx: ctx, bankKeeper: bankKeeper, - balances: make(map[common.Address]map[common.Address]*Balance), } } } @@ -98,7 +85,8 @@ func (bc *BankContract) Run(evm *vm.EVM, input []byte, caller common.Address, va // parse input methodID := input[:4] - if bytes.Equal(methodID, MintMethod.ID) { + switch string(methodID) { + case string(MintMethod.ID): if readonly { return nil, errors.New("the method is not readonly") } @@ -111,107 +99,33 @@ func (bc *BankContract) Run(evm *vm.EVM, input []byte, caller common.Address, va if amount.Sign() <= 0 { return nil, errors.New("invalid amount") } - - if _, ok := bc.balances[caller]; !ok { - bc.balances[caller] = make(map[common.Address]*Balance) - } - balances := bc.balances[caller] - if balance, ok := balances[recipient]; ok { - balance.DirtyAmount = new(big.Int).Add(balance.DirtyAmount, amount) - } else { - // query original amount + denom := EVMDenom(caller) + err = stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { addr := sdk.AccAddress(recipient.Bytes()) - originAmount := bc.bankKeeper.GetBalance(bc.ctx, addr, EVMDenom(caller)).Amount.BigInt() - dirtyAmount := new(big.Int).Add(originAmount, amount) - balances[recipient] = &Balance{ - OriginAmount: originAmount, - DirtyAmount: dirtyAmount, + amt := sdk.NewCoins(sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount))) + if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, amt); err != nil { + return sdkerrors.Wrap(err, "fail to mint coins in precompiled contract") + } + if err := bc.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, amt); err != nil { + return sdkerrors.Wrap(err, "fail to send mint coins to account") } + return nil + }) + if err != nil { + return nil, err } - stateDB.AppendJournalEntry(bankMintChange{bc: bc, caller: caller, recipient: recipient, amount: amount}) - } else if bytes.Equal(methodID, BalanceOfMethod.ID) { + case string(BalanceOfMethod.ID): args, err := BalanceOfMethod.Inputs.Unpack(input[4:]) if err != nil { return nil, errors.New("fail to unpack input arguments") } token := args[0].(common.Address) addr := args[1].(common.Address) - if balances, ok := bc.balances[token]; ok { - if balance, ok := balances[addr]; ok { - return BalanceOfMethod.Outputs.Pack(balance.DirtyAmount) - } - } // query from storage amount := bc.bankKeeper.GetBalance(bc.ctx, sdk.AccAddress(addr.Bytes()), EVMDenom(token)).Amount.BigInt() return BalanceOfMethod.Outputs.Pack(amount) - } else { + default: return nil, errors.New("unknown method") } return nil, nil } - -func (bc *BankContract) Commit(ctx sdk.Context) error { - // sorted iteration - sortedContracts := make([]common.Address, len(bc.balances)) - i := 0 - for contract := range bc.balances { - sortedContracts[i] = contract - i++ - } - sort.Slice(sortedContracts, func(i, j int) bool { - return bytes.Compare(sortedContracts[i].Bytes(), sortedContracts[j].Bytes()) < 0 - }) - for _, contract := range sortedContracts { - denom := EVMDenom(contract) - balances := bc.balances[contract] - sortedRecipients := make([]common.Address, len(balances)) - i = 0 - for recipient := range balances { - sortedRecipients[i] = recipient - i++ - } - sort.Slice(sortedRecipients, func(i, j int) bool { - return bytes.Compare(sortedRecipients[i].Bytes(), sortedRecipients[j].Bytes()) < 0 - }) - for _, recipient := range sortedRecipients { - cosmosAddr := sdk.AccAddress(recipient.Bytes()) - changed := balances[recipient].Changed() - switch changed.Sign() { - case 1: - amt := sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(changed))) - if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, amt); err != nil { - return sdkerrors.Wrap(err, "fail to mint coins in precompiled contract") - } - if err := bc.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, cosmosAddr, amt); err != nil { - return sdkerrors.Wrap(err, "fail to send mint coins to account") - } - case -1: - amt := sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(new(big.Int).Neg(changed)))) - if err := bc.bankKeeper.SendCoinsFromAccountToModule(ctx, cosmosAddr, types.ModuleName, amt); err != nil { - return sdkerrors.Wrap(err, "fail to send burn coins to module") - } - if err := bc.bankKeeper.BurnCoins(ctx, types.ModuleName, amt); err != nil { - return sdkerrors.Wrap(err, "fail to burn coins in precompiled contract") - } - - } - } - } - return nil -} - -type bankMintChange struct { - bc *BankContract - caller common.Address - recipient common.Address - amount *big.Int -} - -func (ch bankMintChange) Revert(*statedb.StateDB) { - balance := ch.bc.balances[ch.caller][ch.recipient] - balance.DirtyAmount = new(big.Int).Sub(balance.DirtyAmount, ch.amount) -} - -func (ch bankMintChange) Dirtied() *common.Address { - return nil -} diff --git a/x/evm/keeper/precompiles/types.go b/x/evm/keeper/precompiles/types.go index 1a69810727..7b1eb09ef2 100644 --- a/x/evm/keeper/precompiles/types.go +++ b/x/evm/keeper/precompiles/types.go @@ -1,8 +1,8 @@ package precompiles -import "github.com/evmos/ethermint/x/evm/statedb" +import sdk "github.com/cosmos/cosmos-sdk/types" // ExtStateDB defines extra methods of statedb to support stateful precompiled contracts type ExtStateDB interface { - AppendJournalEntry(statedb.JournalEntry) + ExecuteNativeAction(action func(ctx sdk.Context) error) error } diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index c51a202250..d628141811 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -16,9 +16,7 @@ package keeper import ( - "bytes" "math/big" - "sort" tmtypes "github.com/tendermint/tendermint/types" @@ -341,23 +339,11 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, // construct precompiles contracts := make(map[common.Address]statedb.StatefulPrecompiledContract, len(k.precompiles)) - sortedContracts := make([]common.Address, 0, len(k.precompiles)) for addr, creator := range k.precompiles { c := creator(ctx) contracts[addr] = c - sortedContracts = append(sortedContracts, addr) } - - sort.Slice(sortedContracts, func(i, j int) bool { - return bytes.Compare(sortedContracts[i].Bytes(), sortedContracts[j].Bytes()) < 0 - }) - - extStates := make([]statedb.ExtState, len(sortedContracts)) - for i, addr := range sortedContracts { - extStates[i] = contracts[addr] - } - - stateDB := k.StateDB(ctx, txConfig, extStates) + stateDB := k.StateDB(ctx, txConfig) evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB, contracts) leftoverGas := msg.Gas() @@ -433,7 +419,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, return nil, errorsmod.Wrap(err, "failed to commit stateDB") } - ctx.MultiStore().Restore(dbCtx.MultiStore()) + ctx.MultiStore().Restore(dbCtx.MultiStore()) // set top stack to store } // calculate a minimum amount of gas to be charged to sender if GasLimit diff --git a/x/evm/statedb/precompiles.go b/x/evm/statedb/precompiles.go index 512836d500..1176fdc39c 100644 --- a/x/evm/statedb/precompiles.go +++ b/x/evm/statedb/precompiles.go @@ -8,7 +8,6 @@ import ( // StatefulPrecompiledContract is a stateful precompiled contract in evm type StatefulPrecompiledContract interface { vm.PrecompiledContract - ExtState } type PrecompiledContractCreator func(sdk.Context) StatefulPrecompiledContract diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index eafaf5990c..88416ea2a7 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -65,13 +65,10 @@ type StateDB struct { // Per-transaction access list accessList *accessList - - // extended states - extStates []ExtState } // New creates a new state from a given trie. -func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig, extStates []ExtState) *StateDB { +func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { statedb := &StateDB{ keeper: keeper, ctx: ctx, @@ -79,8 +76,7 @@ func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig, extStates []ExtState journal: newJournal(), accessList: newAccessList(), - txConfig: txConfig, - extStates: extStates, + txConfig: txConfig, } return statedb @@ -314,7 +310,7 @@ func (s *StateDB) restoreNativeState(ms sdk.CacheMultiStore) { s.ctx = s.ctx.WithMultiStore(ms) } -func (s *StateDB) executeNativeAction(action func(ctx sdk.Context) error) error { +func (s *StateDB) ExecuteNativeAction(action func(ctx sdk.Context) error) error { snapshot := s.ctx.MultiStore().Clone() err := action(s.ctx) if err != nil { @@ -501,11 +497,5 @@ func (s *StateDB) Commit() (sdk.Context, error) { } } } - // commit the extended states - for _, ext := range s.extStates { - if err := ext.Commit(s.ctx); err != nil { - return s.ctx, err - } - } return s.ctx, nil } From a98437de1e0be0c868b170812dc9516dae6f1594 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 31 Jan 2023 09:33:59 +0800 Subject: [PATCH 05/41] avoid inject precompile contracts --- app/app.go | 6 ++--- go.mod | 2 +- go.sum | 4 +-- gomod2nix.toml | 4 +-- x/evm/keeper/keeper.go | 11 +++----- x/evm/keeper/precompiles/bank.go | 8 +++--- x/evm/keeper/state_transition.go | 46 +++++++++++++++++++++++--------- x/evm/statedb/precompiles.go | 13 --------- x/evm/vm/interface.go | 5 +++- 9 files changed, 53 insertions(+), 46 deletions(-) delete mode 100644 x/evm/statedb/precompiles.go diff --git a/app/app.go b/app/app.go index 4fa452930a..b8d1eb9206 100644 --- a/app/app.go +++ b/app/app.go @@ -126,8 +126,8 @@ import ( "github.com/evmos/ethermint/x/evm" evmkeeper "github.com/evmos/ethermint/x/evm/keeper" "github.com/evmos/ethermint/x/evm/keeper/precompiles" - "github.com/evmos/ethermint/x/evm/statedb" evmtypes "github.com/evmos/ethermint/x/evm/types" + "github.com/evmos/ethermint/x/evm/vm" "github.com/evmos/ethermint/x/evm/vm/geth" "github.com/evmos/ethermint/x/feemarket" feemarketkeeper "github.com/evmos/ethermint/x/feemarket/keeper" @@ -421,7 +421,7 @@ func NewEthermintApp( appCodec, authtypes.NewModuleAddress(govtypes.ModuleName), keys[feemarkettypes.StoreKey], tkeys[feemarkettypes.TransientKey], feeMarketSs, ) - contracts := map[common.Address]statedb.PrecompiledContractCreator{ + contracts := map[common.Address]vm.PrecompiledContractCreator{ common.BigToAddress(big.NewInt(100)): precompiles.NewBankContractCreator(app.BankKeeper), } // Set authority to x/gov module account to only expect the module account to update params @@ -429,7 +429,7 @@ func NewEthermintApp( app.EvmKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.FeeMarketKeeper, - nil, geth.NewEVM, tracer, evmSs, contracts, + contracts, geth.NewEVM, tracer, evmSs, ) // Create IBC Keeper diff --git a/go.mod b/go.mod index 0c021972e3..1aad437b11 100644 --- a/go.mod +++ b/go.mod @@ -194,7 +194,7 @@ replace ( // use cosmos keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 github.com/cosmos/cosmos-sdk => github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49 - github.com/ethereum/go-ethereum => github.com/mmsqe/go-ethereum v1.10.26-0.20230127080103-3a00e53a24bb + github.com/ethereum/go-ethereum => github.com/mmsqe/go-ethereum v1.10.26-0.20230131012941-1ed879b1a4a9 // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.0 diff --git a/go.sum b/go.sum index 7457677741..3637c522df 100644 --- a/go.sum +++ b/go.sum @@ -718,8 +718,8 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49 h1:xyzaLfcVtXPuJcESBsl8FGFhgth+2YNHWyzo9a8lPnU= github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49/go.mod h1:rL1mzp5+40g7djg1S1KMPoeg963PUARW9L2hC1pQqOI= -github.com/mmsqe/go-ethereum v1.10.26-0.20230127080103-3a00e53a24bb h1:WXZVxmbB/oIpjVMzdhsxhuvXwXiJvFlUqbpQ+Na/0SI= -github.com/mmsqe/go-ethereum v1.10.26-0.20230127080103-3a00e53a24bb/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= +github.com/mmsqe/go-ethereum v1.10.26-0.20230131012941-1ed879b1a4a9 h1:TQ4XAxfaSmG7AxZ4wV3X6pASA6J704pZv7NMSCIYV8g= +github.com/mmsqe/go-ethereum v1.10.26-0.20230131012941-1ed879b1a4a9/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/gomod2nix.toml b/gomod2nix.toml index 8ec2bb911b..cf00edfbd5 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -160,8 +160,8 @@ schema = 3 version = "v1.0.0" hash = "sha256-k1DYvCqO3BKNcGEve/nMW0RxzMkK2tGfXbUbycqcVSo=" [mod."github.com/ethereum/go-ethereum"] - version = "v1.10.26-0.20230127080103-3a00e53a24bb" - hash = "sha256-UCj5SaNjB/4Q9nI3DSx/HLYr22QHouzYyQ0Kw3KEXz0=" + version = "v1.10.26-0.20230131012941-1ed879b1a4a9" + hash = "sha256-5m+CG3syk9no51Okj0tSHJKCkvKGYDtpunF+SDqxhnc=" replaced = "github.com/mmsqe/go-ethereum" [mod."github.com/felixge/httpsnoop"] version = "v1.0.1" diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index ddc6e4b07f..aec7157c29 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -71,16 +71,13 @@ type Keeper struct { // EVM Hooks for tx post-processing hooks types.EvmHooks - // custom stateless precompiled smart contracts - customPrecompiles evm.PrecompiledContracts + // custom precompiled smart contracts + customPrecompiles map[common.Address]evm.PrecompiledContractCreator // evm constructor function evmConstructor evm.Constructor // Legacy subspace ss paramstypes.Subspace - - // Stateful EVM precompiled contracts - precompiles map[common.Address]statedb.PrecompiledContractCreator } // NewKeeper generates new evm module keeper @@ -92,11 +89,10 @@ func NewKeeper( bankKeeper types.BankKeeper, sk types.StakingKeeper, fmk types.FeeMarketKeeper, - customPrecompiles evm.PrecompiledContracts, + customPrecompiles map[common.Address]evm.PrecompiledContractCreator, evmConstructor evm.Constructor, tracer string, ss paramstypes.Subspace, - precompiles map[common.Address]statedb.PrecompiledContractCreator, ) *Keeper { // ensure evm module account is set if addr := ak.GetModuleAddress(types.ModuleName); addr == nil { @@ -122,7 +118,6 @@ func NewKeeper( evmConstructor: evmConstructor, tracer: tracer, ss: ss, - precompiles: precompiles, } } diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index c8b8ca545d..cb5b079154 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -11,8 +11,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/evmos/ethermint/x/evm/statedb" "github.com/evmos/ethermint/x/evm/types" + evm "github.com/evmos/ethermint/x/evm/vm" ) const EVMDenomPrefix = "evm/" @@ -21,7 +21,7 @@ var ( MintMethod abi.Method BalanceOfMethod abi.Method - _ statedb.StatefulPrecompiledContract = (*BankContract)(nil) + _ evm.StatefulPrecompiledContract = (*BankContract)(nil) ) func init() { @@ -62,8 +62,8 @@ type BankContract struct { } // NewBankContractCreator creates the precompiled contract to manage native tokens -func NewBankContractCreator(bankKeeper types.BankKeeper) statedb.PrecompiledContractCreator { - return func(ctx sdk.Context) statedb.StatefulPrecompiledContract { +func NewBankContractCreator(bankKeeper types.BankKeeper) evm.PrecompiledContractCreator { + return func(ctx sdk.Context) evm.StatefulPrecompiledContract { return &BankContract{ ctx: ctx, bankKeeper: bankKeeper, diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index d628141811..e8fc2410a2 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -17,6 +17,7 @@ package keeper import ( "math/big" + "sync" tmtypes "github.com/tendermint/tendermint/types" @@ -33,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + evm "github.com/evmos/ethermint/x/evm/vm" ) // NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters @@ -44,14 +46,42 @@ import ( // RANDAO implementation. See https://github.com/evmos/ethermint/pull/1520#pullrequestreview-1200504697 // for more information. +var contractLock sync.Mutex + +func appendNewContracts( + chainRules params.Rules, + contracts map[common.Address]evm.StatefulPrecompiledContract, +) { + contractLock.Lock() + defer contractLock.Unlock() + + var precompiles map[common.Address]vm.PrecompiledContract + switch { + case chainRules.IsBerlin: + precompiles = vm.PrecompiledContractsBerlin + case chainRules.IsIstanbul: + precompiles = vm.PrecompiledContractsIstanbul + case chainRules.IsByzantium: + precompiles = vm.PrecompiledContractsByzantium + default: + precompiles = vm.PrecompiledContractsHomestead + } + for addr, c := range contracts { + precompiles[addr] = c + } +} + func (k *Keeper) NewEVM( ctx sdk.Context, msg core.Message, cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB, - contracts map[common.Address]statedb.StatefulPrecompiledContract, + contracts map[common.Address]evm.StatefulPrecompiledContract, ) *vm.EVM { + rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil) + appendNewContracts(rules, contracts) + blockCtx := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, @@ -70,15 +100,7 @@ func (k *Keeper) NewEVM( tracer = k.Tracer(ctx, msg, cfg.ChainConfig) } vmConfig := k.VMConfig(ctx, msg, cfg, tracer) - rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil) - precompiles := make(map[common.Address]vm.PrecompiledContract) - for addr, c := range vm.ActivePrecompiledContracts(rules) { - precompiles[addr] = c - } - for addr, c := range contracts { - precompiles[addr] = c - } - return vm.NewEVMWithPrecompiles(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig, precompiles) + return vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig) } // GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases: @@ -338,8 +360,8 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, } // construct precompiles - contracts := make(map[common.Address]statedb.StatefulPrecompiledContract, len(k.precompiles)) - for addr, creator := range k.precompiles { + contracts := make(map[common.Address]evm.StatefulPrecompiledContract, len(k.customPrecompiles)) + for addr, creator := range k.customPrecompiles { c := creator(ctx) contracts[addr] = c } diff --git a/x/evm/statedb/precompiles.go b/x/evm/statedb/precompiles.go deleted file mode 100644 index 1176fdc39c..0000000000 --- a/x/evm/statedb/precompiles.go +++ /dev/null @@ -1,13 +0,0 @@ -package statedb - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/core/vm" -) - -// StatefulPrecompiledContract is a stateful precompiled contract in evm -type StatefulPrecompiledContract interface { - vm.PrecompiledContract -} - -type PrecompiledContractCreator func(sdk.Context) StatefulPrecompiledContract diff --git a/x/evm/vm/interface.go b/x/evm/vm/interface.go index 41dcd61ded..8b26b26755 100644 --- a/x/evm/vm/interface.go +++ b/x/evm/vm/interface.go @@ -18,6 +18,7 @@ package vm import ( "math/big" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -27,9 +28,11 @@ import ( // PrecompiledContracts defines a map of address -> precompiled contract type PrecompiledContracts map[common.Address]vm.PrecompiledContract +type PrecompiledContractCreator func(sdk.Context) StatefulPrecompiledContract + type StatefulPrecompiledContract interface { vm.PrecompiledContract - RunStateful(evm EVM, addr common.Address, input []byte, value *big.Int) (ret []byte, err error) + // RunStateful(evm EVM, addr common.Address, input []byte, value *big.Int) (ret []byte, err error) } // EVM defines the interface for the Ethereum Virtual Machine used by the EVM module. From bb913f17c72c1a0f3188b32b009144b831f91580 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 31 Jan 2023 15:43:30 +0800 Subject: [PATCH 06/41] avoid fork --- go.mod | 1 - go.sum | 65 ++++++++++++++++++++----------- gomod2nix.toml | 5 +-- x/evm/keeper/precompiles/bank.go | 30 ++++++++------ x/evm/keeper/precompiles/types.go | 8 ---- x/evm/keeper/state_transition.go | 15 +++---- x/evm/vm/geth/geth.go | 6 +-- x/evm/vm/interface.go | 17 ++++++-- 8 files changed, 85 insertions(+), 62 deletions(-) delete mode 100644 x/evm/keeper/precompiles/types.go diff --git a/go.mod b/go.mod index 1aad437b11..00d6ec53b4 100644 --- a/go.mod +++ b/go.mod @@ -194,7 +194,6 @@ replace ( // use cosmos keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 github.com/cosmos/cosmos-sdk => github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49 - github.com/ethereum/go-ethereum => github.com/mmsqe/go-ethereum v1.10.26-0.20230131012941-1ed879b1a4a9 // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.0 diff --git a/go.sum b/go.sum index 3637c522df..295a43d5d0 100644 --- a/go.sum +++ b/go.sum @@ -59,12 +59,24 @@ filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmG filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= +github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= @@ -144,9 +156,10 @@ github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd v0.22.2 h1:vBZ+lGGd1XubpOWO67ITJpAEsICWhA0YzqkcpkgNBfo= github.com/btcsuite/btcd v0.22.2/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -250,6 +263,7 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= @@ -273,13 +287,16 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf h1:Yt+4K30SdjOkRoRRm3vYNQgR+/ZIy0RmeUDZo7Y8zeQ= github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= @@ -302,6 +319,10 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.10.4/go.mod h1:nEE0TP5MtxGzOMd7egIrbPJMQBnhVU3ELNxhBglIzhg= +github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= +github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= +github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= @@ -309,7 +330,6 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -321,7 +341,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= @@ -365,6 +384,7 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -424,6 +444,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -471,6 +492,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -494,6 +516,7 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= @@ -560,6 +583,8 @@ github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= +github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= @@ -582,6 +607,7 @@ github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19y github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= @@ -615,6 +641,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= @@ -642,7 +669,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= @@ -667,6 +693,7 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -674,8 +701,11 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -718,8 +748,6 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49 h1:xyzaLfcVtXPuJcESBsl8FGFhgth+2YNHWyzo9a8lPnU= github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49/go.mod h1:rL1mzp5+40g7djg1S1KMPoeg963PUARW9L2hC1pQqOI= -github.com/mmsqe/go-ethereum v1.10.26-0.20230131012941-1ed879b1a4a9 h1:TQ4XAxfaSmG7AxZ4wV3X6pASA6J704pZv7NMSCIYV8g= -github.com/mmsqe/go-ethereum v1.10.26-0.20230131012941-1ed879b1a4a9/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -756,7 +784,6 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= @@ -942,13 +969,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= @@ -995,7 +1021,6 @@ github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= -github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -1006,14 +1031,12 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.1 h1:Pip65OOl4iJ84WTpA4BKChvOufMhhbxED3BaihoZN4c= @@ -1062,7 +1085,6 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= @@ -1080,7 +1102,6 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -1108,13 +1129,12 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1166,7 +1186,6 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1270,6 +1289,7 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1277,7 +1297,6 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= @@ -1330,7 +1349,6 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1365,13 +1383,11 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -1513,9 +1529,11 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1531,6 +1549,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/gomod2nix.toml b/gomod2nix.toml index cf00edfbd5..d10ee398ca 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -160,9 +160,8 @@ schema = 3 version = "v1.0.0" hash = "sha256-k1DYvCqO3BKNcGEve/nMW0RxzMkK2tGfXbUbycqcVSo=" [mod."github.com/ethereum/go-ethereum"] - version = "v1.10.26-0.20230131012941-1ed879b1a4a9" - hash = "sha256-5m+CG3syk9no51Okj0tSHJKCkvKGYDtpunF+SDqxhnc=" - replaced = "github.com/mmsqe/go-ethereum" + version = "v1.10.26" + hash = "sha256-gkMEwJ4rOgn12amD4QpZ4th/10uyTTeoFmpseuKDQPs=" [mod."github.com/felixge/httpsnoop"] version = "v1.0.1" hash = "sha256-TNXnnC/ZGNY9lInAcES1cBGqIdEljKuh5LH/khVFjVk=" diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index cb5b079154..62d0c67f80 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -7,7 +7,6 @@ import ( sdkmath "cosmossdk.io/math" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -59,14 +58,28 @@ func EVMDenom(token common.Address) string { type BankContract struct { ctx sdk.Context bankKeeper types.BankKeeper + stateDB evm.ExtStateDB + caller common.Address + value *big.Int + readonly bool } // NewBankContractCreator creates the precompiled contract to manage native tokens func NewBankContractCreator(bankKeeper types.BankKeeper) evm.PrecompiledContractCreator { - return func(ctx sdk.Context) evm.StatefulPrecompiledContract { + return func( + ctx sdk.Context, + stateDB evm.ExtStateDB, + caller common.Address, + value *big.Int, + readonly bool, + ) evm.StatefulPrecompiledContract { return &BankContract{ ctx: ctx, bankKeeper: bankKeeper, + stateDB: stateDB, + caller: caller, + value: value, + readonly: readonly, } } } @@ -77,17 +90,12 @@ func (bc *BankContract) RequiredGas(input []byte) uint64 { return 0 } -func (bc *BankContract) Run(evm *vm.EVM, input []byte, caller common.Address, value *big.Int, readonly bool) ([]byte, error) { - stateDB, ok := evm.StateDB.(ExtStateDB) - if !ok { - return nil, errors.New("not run in ethermint") - } - +func (bc *BankContract) Run(input []byte) ([]byte, error) { // parse input methodID := input[:4] switch string(methodID) { case string(MintMethod.ID): - if readonly { + if bc.readonly { return nil, errors.New("the method is not readonly") } args, err := MintMethod.Inputs.Unpack(input[4:]) @@ -99,8 +107,8 @@ func (bc *BankContract) Run(evm *vm.EVM, input []byte, caller common.Address, va if amount.Sign() <= 0 { return nil, errors.New("invalid amount") } - denom := EVMDenom(caller) - err = stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { + denom := EVMDenom(bc.caller) + err = bc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { addr := sdk.AccAddress(recipient.Bytes()) amt := sdk.NewCoins(sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount))) if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, amt); err != nil { diff --git a/x/evm/keeper/precompiles/types.go b/x/evm/keeper/precompiles/types.go deleted file mode 100644 index 7b1eb09ef2..0000000000 --- a/x/evm/keeper/precompiles/types.go +++ /dev/null @@ -1,8 +0,0 @@ -package precompiles - -import sdk "github.com/cosmos/cosmos-sdk/types" - -// ExtStateDB defines extra methods of statedb to support stateful precompiled contracts -type ExtStateDB interface { - ExecuteNativeAction(action func(ctx sdk.Context) error) error -} diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index e8fc2410a2..4f52f560fb 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -359,17 +359,20 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, return nil, errorsmod.Wrap(types.ErrCallDisabled, "failed to call contract") } + contractCreation := msg.To() == nil + sender := vm.AccountRef(msg.From()) + caller := sender.Address() + if !contractCreation { + caller = *msg.To() + } // construct precompiles contracts := make(map[common.Address]evm.StatefulPrecompiledContract, len(k.customPrecompiles)) + stateDB := k.StateDB(ctx, txConfig) for addr, creator := range k.customPrecompiles { - c := creator(ctx) - contracts[addr] = c + contracts[addr] = creator(ctx, stateDB, caller, msg.Value(), false) } - stateDB := k.StateDB(ctx, txConfig) evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB, contracts) - leftoverGas := msg.Gas() - // Allow the tracer captures the tx level events, mainly the gas consumption. vmCfg := evm.Config if vmCfg.Debug { @@ -379,8 +382,6 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, }() } - sender := vm.AccountRef(msg.From()) - contractCreation := msg.To() == nil isLondon := cfg.ChainConfig.IsLondon(evm.Context.BlockNumber) intrinsicGas, err := k.GetEthIntrinsicGas(ctx, msg, cfg.ChainConfig, contractCreation) diff --git a/x/evm/vm/geth/geth.go b/x/evm/vm/geth/geth.go index c188d5d15b..3104d62c75 100644 --- a/x/evm/vm/geth/geth.go +++ b/x/evm/vm/geth/geth.go @@ -16,8 +16,6 @@ package geth import ( - "math/big" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -85,10 +83,8 @@ func (EVM) ActivePrecompiles(rules params.Rules) []common.Address { // value arguments. It uses the RunPrecompiledContract function from the geth vm package func (e EVM) RunPrecompiledContract( p evm.StatefulPrecompiledContract, - _ common.Address, // address arg is unused input []byte, suppliedGas uint64, - caller common.Address, value *big.Int, readonly bool, ) (ret []byte, remainingGas uint64, err error) { - return vm.RunPrecompiledContract(p, e.EVM, input, suppliedGas, caller, value, readonly) + return vm.RunPrecompiledContract(p, input, suppliedGas) } diff --git a/x/evm/vm/interface.go b/x/evm/vm/interface.go index 8b26b26755..1fe7ce511f 100644 --- a/x/evm/vm/interface.go +++ b/x/evm/vm/interface.go @@ -28,13 +28,24 @@ import ( // PrecompiledContracts defines a map of address -> precompiled contract type PrecompiledContracts map[common.Address]vm.PrecompiledContract -type PrecompiledContractCreator func(sdk.Context) StatefulPrecompiledContract +type PrecompiledContractCreator func( + sdk.Context, + ExtStateDB, + common.Address, + *big.Int, + bool, +) StatefulPrecompiledContract type StatefulPrecompiledContract interface { vm.PrecompiledContract // RunStateful(evm EVM, addr common.Address, input []byte, value *big.Int) (ret []byte, err error) } +// ExtStateDB defines extra methods of statedb to support stateful precompiled contracts +type ExtStateDB interface { + ExecuteNativeAction(action func(ctx sdk.Context) error) error +} + // EVM defines the interface for the Ethereum Virtual Machine used by the EVM module. type EVM interface { Config() vm.Config @@ -64,10 +75,8 @@ type EVM interface { Precompile(addr common.Address) (vm.PrecompiledContract, bool) RunPrecompiledContract( p StatefulPrecompiledContract, - addr common.Address, input []byte, - suppliedGas uint64, - caller common.Address, value *big.Int, readonly bool) ( + suppliedGas uint64) ( ret []byte, remainingGas uint64, err error, ) } From f8bac9b39885adfcf8c83f336b2a02319fe67105 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Wed, 1 Feb 2023 23:04:37 +0800 Subject: [PATCH 07/41] check readonly per run instead of contract --- x/evm/keeper/precompiles/bank.go | 13 +++++++------ x/evm/keeper/precompiles/utils.go | 27 +++++++++++++++++++++++++++ x/evm/keeper/state_transition.go | 2 +- x/evm/vm/interface.go | 1 - 4 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 x/evm/keeper/precompiles/utils.go diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index 62d0c67f80..5e0228d78d 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -61,7 +61,6 @@ type BankContract struct { stateDB evm.ExtStateDB caller common.Address value *big.Int - readonly bool } // NewBankContractCreator creates the precompiled contract to manage native tokens @@ -71,7 +70,6 @@ func NewBankContractCreator(bankKeeper types.BankKeeper) evm.PrecompiledContract stateDB evm.ExtStateDB, caller common.Address, value *big.Int, - readonly bool, ) evm.StatefulPrecompiledContract { return &BankContract{ ctx: ctx, @@ -79,7 +77,6 @@ func NewBankContractCreator(bankKeeper types.BankKeeper) evm.PrecompiledContract stateDB: stateDB, caller: caller, value: value, - readonly: readonly, } } } @@ -91,11 +88,15 @@ func (bc *BankContract) RequiredGas(input []byte) uint64 { } func (bc *BankContract) Run(input []byte) ([]byte, error) { + readonly, err := CheckCaller() + if err != nil { + return nil, err + } // parse input methodID := input[:4] switch string(methodID) { case string(MintMethod.ID): - if bc.readonly { + if readonly { return nil, errors.New("the method is not readonly") } args, err := MintMethod.Inputs.Unpack(input[4:]) @@ -130,8 +131,8 @@ func (bc *BankContract) Run(input []byte) ([]byte, error) { token := args[0].(common.Address) addr := args[1].(common.Address) // query from storage - amount := bc.bankKeeper.GetBalance(bc.ctx, sdk.AccAddress(addr.Bytes()), EVMDenom(token)).Amount.BigInt() - return BalanceOfMethod.Outputs.Pack(amount) + balance := bc.bankKeeper.GetBalance(bc.ctx, sdk.AccAddress(addr.Bytes()), EVMDenom(token)).Amount.BigInt() + return BalanceOfMethod.Outputs.Pack(balance) default: return nil, errors.New("unknown method") } diff --git a/x/evm/keeper/precompiles/utils.go b/x/evm/keeper/precompiles/utils.go new file mode 100644 index 0000000000..bb767708a6 --- /dev/null +++ b/x/evm/keeper/precompiles/utils.go @@ -0,0 +1,27 @@ +package precompiles + +import ( + "errors" + "runtime" + "strings" +) + +func CheckCaller() (bool, error) { + layer := 3 + pc, _, _, _ := runtime.Caller(layer) + prefix := "github.com/ethereum/go-ethereum/core/vm.(*EVM)." + caller := runtime.FuncForPC(pc).Name() + readonly := false + if strings.Index(caller, prefix) == 0 { + fn := caller[len(prefix):] + switch fn { + case "Call": + readonly = false + case "CallCode", "DelegateCall", "StaticCall": + readonly = true + default: + return readonly, errors.New("unknown caller") + } + } + return readonly, nil +} diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 4f52f560fb..26c7fe7d67 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -369,7 +369,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, contracts := make(map[common.Address]evm.StatefulPrecompiledContract, len(k.customPrecompiles)) stateDB := k.StateDB(ctx, txConfig) for addr, creator := range k.customPrecompiles { - contracts[addr] = creator(ctx, stateDB, caller, msg.Value(), false) + contracts[addr] = creator(ctx, stateDB, caller, msg.Value()) } evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB, contracts) leftoverGas := msg.Gas() diff --git a/x/evm/vm/interface.go b/x/evm/vm/interface.go index 1fe7ce511f..77ef88c329 100644 --- a/x/evm/vm/interface.go +++ b/x/evm/vm/interface.go @@ -33,7 +33,6 @@ type PrecompiledContractCreator func( ExtStateDB, common.Address, *big.Int, - bool, ) StatefulPrecompiledContract type StatefulPrecompiledContract interface { From aa03962ee4b3f175376f5ca860bde63c47af738e Mon Sep 17 00:00:00 2001 From: mmsqe Date: Fri, 3 Feb 2023 22:35:29 +0800 Subject: [PATCH 08/41] test delegatecall --- .../hardhat/contracts/TestBank.sol | 17 +++++++++++++++++ tests/integration_tests/test_precompiles.py | 15 ++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/integration_tests/hardhat/contracts/TestBank.sol b/tests/integration_tests/hardhat/contracts/TestBank.sol index 20b3eff9e7..747544261a 100644 --- a/tests/integration_tests/hardhat/contracts/TestBank.sol +++ b/tests/integration_tests/hardhat/contracts/TestBank.sol @@ -20,4 +20,21 @@ contract TestBank { nativeMint(amount); revert("test"); } + function nativeMintDelegate(uint amount) public { + (bool result, bytes memory _data) = bankContract.delegatecall(abi.encodeWithSignature( + "mint(address,uint256)", msg.sender, amount + )); + require(result, "native delegatecall"); + } + function nativeBalanceOfDelegate(address addr) public returns (uint) { + (bool result, bytes memory data) = bankContract.delegatecall(abi.encodeWithSignature( + "balanceOf(address,address)", address(this), addr + )); + require(result, "native delegatecall"); + return abi.decode(data, (uint)); + } + function nativeMintRevertDelegate(uint amount) public { + nativeMintDelegate(amount); + revert("test"); + } } diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index 93a7bac946..11b2bc8652 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -1,30 +1,35 @@ +import pytest + from .utils import ADDRS, CONTRACTS, deploy_contract, eth_to_bech32, send_transaction -def test_precompiles(ethermint): +@pytest.mark.parametrize("suffix", [""]) +def test_precompiles(ethermint, suffix): w3 = ethermint.w3 addr = ADDRS["validator"] amount = 100 contract, _ = deploy_contract(w3, CONTRACTS["TestBank"]) - tx = contract.functions.nativeMint(amount).build_transaction({"from": addr}) + data = {"from": addr} + tx = contract.functions["nativeMint" + suffix](amount).build_transaction(data) receipt = send_transaction(w3, tx) assert receipt.status == 1, "expect success" # query balance through contract - assert contract.caller.nativeBalanceOf(addr) == amount + balance_of = getattr(contract.caller, "nativeBalanceOf" + suffix) + assert balance_of(addr) == amount # query balance through cosmos rpc cli = ethermint.cosmos_cli() assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount # test exception revert - tx = contract.functions.nativeMintRevert(amount).build_transaction( + tx = contract.functions["nativeMintRevert" + suffix](amount).build_transaction( {"from": addr, "gas": 210000} ) receipt = send_transaction(w3, tx) assert receipt.status == 0, "expect failure" # check balance don't change - assert contract.caller.nativeBalanceOf(addr) == amount + assert balance_of(addr) == amount # query balance through cosmos rpc cli = ethermint.cosmos_cli() assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount From 4337fb46a3fe0385981b7050a0035c46d79fa42e Mon Sep 17 00:00:00 2001 From: mmsqe Date: Sat, 4 Feb 2023 22:13:15 +0800 Subject: [PATCH 09/41] test delegatecall --- .../hardhat/contracts/TestBank.sol | 19 +------- .../hardhat/contracts/TestBankDelegate.sol | 22 +++++++++ tests/integration_tests/test_precompiles.py | 45 +++++++++++++++---- tests/integration_tests/utils.py | 1 + 4 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 tests/integration_tests/hardhat/contracts/TestBankDelegate.sol diff --git a/tests/integration_tests/hardhat/contracts/TestBank.sol b/tests/integration_tests/hardhat/contracts/TestBank.sol index 747544261a..d358f0afc9 100644 --- a/tests/integration_tests/hardhat/contracts/TestBank.sol +++ b/tests/integration_tests/hardhat/contracts/TestBank.sol @@ -20,21 +20,4 @@ contract TestBank { nativeMint(amount); revert("test"); } - function nativeMintDelegate(uint amount) public { - (bool result, bytes memory _data) = bankContract.delegatecall(abi.encodeWithSignature( - "mint(address,uint256)", msg.sender, amount - )); - require(result, "native delegatecall"); - } - function nativeBalanceOfDelegate(address addr) public returns (uint) { - (bool result, bytes memory data) = bankContract.delegatecall(abi.encodeWithSignature( - "balanceOf(address,address)", address(this), addr - )); - require(result, "native delegatecall"); - return abi.decode(data, (uint)); - } - function nativeMintRevertDelegate(uint amount) public { - nativeMintDelegate(amount); - revert("test"); - } -} +} \ No newline at end of file diff --git a/tests/integration_tests/hardhat/contracts/TestBankDelegate.sol b/tests/integration_tests/hardhat/contracts/TestBankDelegate.sol new file mode 100644 index 0000000000..9410e8bf7f --- /dev/null +++ b/tests/integration_tests/hardhat/contracts/TestBankDelegate.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >0.6.6; + +contract TestBankDelegate { + function nativeMint(address _contract, uint amount) public { + (bool result, bytes memory _data) = _contract.delegatecall(abi.encodeWithSignature( + "nativeMint(uint256)", amount + )); + require(result, "native call"); + } + function nativeBalanceOf(address _contract, address addr) public returns (uint) { + (bool result, bytes memory data) = _contract.delegatecall(abi.encodeWithSignature( + "nativeBalanceOf(address)", addr + )); + require(result, "native call"); + return abi.decode(data, (uint)); + } + function nativeMintRevert(address _contract, uint amount) public { + nativeMint(_contract, amount); + revert("test"); + } +} diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index 11b2bc8652..b439da645d 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -1,35 +1,62 @@ -import pytest - from .utils import ADDRS, CONTRACTS, deploy_contract, eth_to_bech32, send_transaction -@pytest.mark.parametrize("suffix", [""]) -def test_precompiles(ethermint, suffix): +def test_call(ethermint): w3 = ethermint.w3 addr = ADDRS["validator"] amount = 100 contract, _ = deploy_contract(w3, CONTRACTS["TestBank"]) + tx = contract.functions.nativeMint(amount).build_transaction({"from": addr}) + receipt = send_transaction(w3, tx) + assert receipt.status == 1, "expect success" + + # query balance through contract + assert contract.caller.nativeBalanceOf(addr) == amount + # query balance through cosmos rpc + cli = ethermint.cosmos_cli() + assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount + + # test exception revert + tx = contract.functions.nativeMintRevert(amount).build_transaction( + {"from": addr, "gas": 210000} + ) + receipt = send_transaction(w3, tx) + assert receipt.status == 0, "expect failure" + + # check balance don't change + assert contract.caller.nativeBalanceOf(addr) == amount + # query balance through cosmos rpc + cli = ethermint.cosmos_cli() + assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount + + +def test_delegate(ethermint): + w3 = ethermint.w3 + addr = ADDRS["validator"] + amount = 100 + _, res = deploy_contract(w3, CONTRACTS["TestBank"]) + bank = res["contractAddress"] + contract, _ = deploy_contract(w3, CONTRACTS["TestBankDelegate"]) data = {"from": addr} - tx = contract.functions["nativeMint" + suffix](amount).build_transaction(data) + tx = contract.functions.nativeMint(bank, amount).build_transaction(data) receipt = send_transaction(w3, tx) assert receipt.status == 1, "expect success" # query balance through contract - balance_of = getattr(contract.caller, "nativeBalanceOf" + suffix) - assert balance_of(addr) == amount + assert contract.caller.nativeBalanceOf(bank, addr) == amount # query balance through cosmos rpc cli = ethermint.cosmos_cli() assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount # test exception revert - tx = contract.functions["nativeMintRevert" + suffix](amount).build_transaction( + tx = contract.functions.nativeMintRevert(bank, amount).build_transaction( {"from": addr, "gas": 210000} ) receipt = send_transaction(w3, tx) assert receipt.status == 0, "expect failure" # check balance don't change - assert balance_of(addr) == amount + assert contract.caller.nativeBalanceOf(bank, addr) == amount # query balance through cosmos rpc cli = ethermint.cosmos_cli() assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount diff --git a/tests/integration_tests/utils.py b/tests/integration_tests/utils.py index 23fa58609a..17e6959495 100644 --- a/tests/integration_tests/utils.py +++ b/tests/integration_tests/utils.py @@ -33,6 +33,7 @@ "Mars": "Mars.sol", "StateContract": "StateContract.sol", "TestBank": "TestBank.sol", + "TestBankDelegate": "TestBankDelegate.sol", } From a740db1829b6f4675c9ec66680e3b6da58d5a34d Mon Sep 17 00:00:00 2001 From: mmsqe Date: Sat, 4 Feb 2023 22:12:51 +0800 Subject: [PATCH 10/41] update geth --- go.mod | 1 + go.sum | 65 +++++++++++-------------------- gomod2nix.toml | 5 ++- x/evm/keeper/precompiles/bank.go | 29 +++++++------- x/evm/keeper/precompiles/utils.go | 27 ------------- x/evm/keeper/state_transition.go | 57 +++++++++------------------ x/evm/types/tracer.go | 2 +- x/evm/vm/geth/geth.go | 25 ++++-------- x/evm/vm/interface.go | 10 +++-- 9 files changed, 73 insertions(+), 148 deletions(-) delete mode 100644 x/evm/keeper/precompiles/utils.go diff --git a/go.mod b/go.mod index 00d6ec53b4..fa33ab9876 100644 --- a/go.mod +++ b/go.mod @@ -194,6 +194,7 @@ replace ( // use cosmos keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 github.com/cosmos/cosmos-sdk => github.com/mmsqe/cosmos-sdk v0.46.6-0.20230110154545-70f48768ca49 + github.com/ethereum/go-ethereum => github.com/evmos/go-ethereum v1.10.26-evmos-rc1 // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.0 diff --git a/go.sum b/go.sum index 295a43d5d0..73ce638236 100644 --- a/go.sum +++ b/go.sum @@ -59,24 +59,12 @@ filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmG filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA= -github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= -github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= -github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= @@ -156,10 +144,9 @@ github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd v0.22.2 h1:vBZ+lGGd1XubpOWO67ITJpAEsICWhA0YzqkcpkgNBfo= github.com/btcsuite/btcd v0.22.2/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= -github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -263,7 +250,6 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= @@ -287,16 +273,13 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= -github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf h1:Yt+4K30SdjOkRoRRm3vYNQgR+/ZIy0RmeUDZo7Y8zeQ= github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= @@ -319,10 +302,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.4/go.mod h1:nEE0TP5MtxGzOMd7egIrbPJMQBnhVU3ELNxhBglIzhg= -github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= -github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= +github.com/evmos/go-ethereum v1.10.26-evmos-rc1 h1:8+jrotZVyO0eIJGRGa1Kga3Fo7DjgFUE9rd6A8Yrbcg= +github.com/evmos/go-ethereum v1.10.26-evmos-rc1/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= @@ -330,6 +311,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -341,6 +323,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= @@ -384,7 +367,6 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -444,7 +426,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -492,7 +473,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -516,7 +496,6 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= @@ -583,8 +562,6 @@ github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= -github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= @@ -607,7 +584,6 @@ github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19y github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= @@ -641,7 +617,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= @@ -669,6 +644,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= @@ -693,7 +669,6 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -701,11 +676,8 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -784,6 +756,7 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= @@ -969,12 +942,13 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= @@ -1021,6 +995,7 @@ github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= +github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -1031,12 +1006,14 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.1 h1:Pip65OOl4iJ84WTpA4BKChvOufMhhbxED3BaihoZN4c= @@ -1085,6 +1062,7 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= @@ -1102,6 +1080,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -1129,12 +1108,13 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1186,6 +1166,7 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1289,7 +1270,6 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1297,6 +1277,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= @@ -1349,6 +1330,7 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1383,11 +1365,13 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -1529,11 +1513,9 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1549,7 +1531,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/gomod2nix.toml b/gomod2nix.toml index d10ee398ca..24129979f3 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -160,8 +160,9 @@ schema = 3 version = "v1.0.0" hash = "sha256-k1DYvCqO3BKNcGEve/nMW0RxzMkK2tGfXbUbycqcVSo=" [mod."github.com/ethereum/go-ethereum"] - version = "v1.10.26" - hash = "sha256-gkMEwJ4rOgn12amD4QpZ4th/10uyTTeoFmpseuKDQPs=" + version = "v1.10.26-evmos-rc1" + hash = "sha256-GgcReGsIIuBE2TabDYqDO9sBGogdVr9RSh4arQzdPnE=" + replaced = "github.com/evmos/go-ethereum" [mod."github.com/felixge/httpsnoop"] version = "v1.0.1" hash = "sha256-TNXnnC/ZGNY9lInAcES1cBGqIdEljKuh5LH/khVFjVk=" diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index 5e0228d78d..5f5ea1eaf0 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -7,6 +7,7 @@ import ( sdkmath "cosmossdk.io/math" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -59,8 +60,6 @@ type BankContract struct { ctx sdk.Context bankKeeper types.BankKeeper stateDB evm.ExtStateDB - caller common.Address - value *big.Int } // NewBankContractCreator creates the precompiled contract to manage native tokens @@ -68,38 +67,38 @@ func NewBankContractCreator(bankKeeper types.BankKeeper) evm.PrecompiledContract return func( ctx sdk.Context, stateDB evm.ExtStateDB, - caller common.Address, - value *big.Int, ) evm.StatefulPrecompiledContract { return &BankContract{ ctx: ctx, bankKeeper: bankKeeper, stateDB: stateDB, - caller: caller, - value: value, } } } +func (bc *BankContract) Address() common.Address { + return common.BytesToAddress([]byte{100}) +} + // RequiredGas calculates the contract gas use func (bc *BankContract) RequiredGas(input []byte) uint64 { // TODO estimate required gas return 0 } -func (bc *BankContract) Run(input []byte) ([]byte, error) { - readonly, err := CheckCaller() - if err != nil { - return nil, err - } +func (bc *BankContract) IsStateful() bool { + return true +} + +func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { // parse input - methodID := input[:4] + methodID := contract.Input[:4] switch string(methodID) { case string(MintMethod.ID): if readonly { return nil, errors.New("the method is not readonly") } - args, err := MintMethod.Inputs.Unpack(input[4:]) + args, err := MintMethod.Inputs.Unpack(contract.Input[4:]) if err != nil { return nil, errors.New("fail to unpack input arguments") } @@ -108,7 +107,7 @@ func (bc *BankContract) Run(input []byte) ([]byte, error) { if amount.Sign() <= 0 { return nil, errors.New("invalid amount") } - denom := EVMDenom(bc.caller) + denom := EVMDenom(contract.CallerAddress) err = bc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { addr := sdk.AccAddress(recipient.Bytes()) amt := sdk.NewCoins(sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount))) @@ -124,7 +123,7 @@ func (bc *BankContract) Run(input []byte) ([]byte, error) { return nil, err } case string(BalanceOfMethod.ID): - args, err := BalanceOfMethod.Inputs.Unpack(input[4:]) + args, err := BalanceOfMethod.Inputs.Unpack(contract.Input[4:]) if err != nil { return nil, errors.New("fail to unpack input arguments") } diff --git a/x/evm/keeper/precompiles/utils.go b/x/evm/keeper/precompiles/utils.go deleted file mode 100644 index bb767708a6..0000000000 --- a/x/evm/keeper/precompiles/utils.go +++ /dev/null @@ -1,27 +0,0 @@ -package precompiles - -import ( - "errors" - "runtime" - "strings" -) - -func CheckCaller() (bool, error) { - layer := 3 - pc, _, _, _ := runtime.Caller(layer) - prefix := "github.com/ethereum/go-ethereum/core/vm.(*EVM)." - caller := runtime.FuncForPC(pc).Name() - readonly := false - if strings.Index(caller, prefix) == 0 { - fn := caller[len(prefix):] - switch fn { - case "Call": - readonly = false - case "CallCode", "DelegateCall", "StaticCall": - readonly = true - default: - return readonly, errors.New("unknown caller") - } - } - return readonly, nil -} diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 26c7fe7d67..8cc0e64202 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -17,7 +17,6 @@ package keeper import ( "math/big" - "sync" tmtypes "github.com/tendermint/tendermint/types" @@ -46,31 +45,6 @@ import ( // RANDAO implementation. See https://github.com/evmos/ethermint/pull/1520#pullrequestreview-1200504697 // for more information. -var contractLock sync.Mutex - -func appendNewContracts( - chainRules params.Rules, - contracts map[common.Address]evm.StatefulPrecompiledContract, -) { - contractLock.Lock() - defer contractLock.Unlock() - - var precompiles map[common.Address]vm.PrecompiledContract - switch { - case chainRules.IsBerlin: - precompiles = vm.PrecompiledContractsBerlin - case chainRules.IsIstanbul: - precompiles = vm.PrecompiledContractsIstanbul - case chainRules.IsByzantium: - precompiles = vm.PrecompiledContractsByzantium - default: - precompiles = vm.PrecompiledContractsHomestead - } - for addr, c := range contracts { - precompiles[addr] = c - } -} - func (k *Keeper) NewEVM( ctx sdk.Context, msg core.Message, @@ -79,9 +53,6 @@ func (k *Keeper) NewEVM( stateDB vm.StateDB, contracts map[common.Address]evm.StatefulPrecompiledContract, ) *vm.EVM { - rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil) - appendNewContracts(rules, contracts) - blockCtx := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, @@ -100,7 +71,20 @@ func (k *Keeper) NewEVM( tracer = k.Tracer(ctx, msg, cfg.ChainConfig) } vmConfig := k.VMConfig(ctx, msg, cfg, tracer) - return vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig) + rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil) + precompiles := make(map[common.Address]vm.PrecompiledContract) + active := make([]common.Address, 0) + for addr, c := range vm.DefaultPrecompiles(rules) { + precompiles[addr] = c + active = append(active, addr) + } + for addr, c := range contracts { + precompiles[addr] = c + active = append(active, addr) + } + evm := vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig) + evm.WithPrecompiles(precompiles, active) + return evm } // GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases: @@ -359,17 +343,11 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, return nil, errorsmod.Wrap(types.ErrCallDisabled, "failed to call contract") } - contractCreation := msg.To() == nil - sender := vm.AccountRef(msg.From()) - caller := sender.Address() - if !contractCreation { - caller = *msg.To() - } // construct precompiles contracts := make(map[common.Address]evm.StatefulPrecompiledContract, len(k.customPrecompiles)) stateDB := k.StateDB(ctx, txConfig) for addr, creator := range k.customPrecompiles { - contracts[addr] = creator(ctx, stateDB, caller, msg.Value()) + contracts[addr] = creator(ctx, stateDB) } evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB, contracts) leftoverGas := msg.Gas() @@ -383,7 +361,8 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, } isLondon := cfg.ChainConfig.IsLondon(evm.Context.BlockNumber) - + contractCreation := msg.To() == nil + sender := vm.AccountRef(msg.From()) intrinsicGas, err := k.GetEthIntrinsicGas(ctx, msg, cfg.ChainConfig, contractCreation) if err != nil { // should have already been checked on Ante Handler @@ -400,7 +379,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, // access list preparation is moved from ante handler to here, because it's needed when `ApplyMessage` is called // under contexts where ante handlers are not run, for example `eth_call` and `eth_estimateGas`. if rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil); rules.IsBerlin { - stateDB.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) + stateDB.PrepareAccessList(msg.From(), msg.To(), vm.DefaultActivePrecompiles(rules), msg.AccessList()) } if contractCreation { diff --git a/x/evm/types/tracer.go b/x/evm/types/tracer.go index 5be5408742..a75a1a085f 100644 --- a/x/evm/types/tracer.go +++ b/x/evm/types/tracer.go @@ -45,7 +45,7 @@ func NewTracer(tracer string, msg core.Message, cfg *params.ChainConfig, height switch tracer { case TracerAccessList: - preCompiles := vm.ActivePrecompiles(cfg.Rules(big.NewInt(height), cfg.MergeNetsplitBlock != nil)) + preCompiles := vm.DefaultActivePrecompiles(cfg.Rules(big.NewInt(height), cfg.MergeNetsplitBlock != nil)) return logger.NewAccessListTracer(msg.AccessList(), msg.From(), *msg.To(), preCompiles) case TracerJSON: return logger.NewJSONLogger(logCfg, os.Stderr) diff --git a/x/evm/vm/geth/geth.go b/x/evm/vm/geth/geth.go index 3104d62c75..ff6b4fc9c1 100644 --- a/x/evm/vm/geth/geth.go +++ b/x/evm/vm/geth/geth.go @@ -16,7 +16,8 @@ package geth import ( - "github.com/ethereum/go-ethereum/common" + "math/big" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -64,27 +65,15 @@ func (e EVM) Config() vm.Config { return e.EVM.Config } -// Precompile returns the precompiled contract associated with the given address -// and the current chain configuration. If the contract cannot be found it returns -// nil. -func (e EVM) Precompile(addr common.Address) (p vm.PrecompiledContract, found bool) { - precompiles := GetPrecompiles(e.ChainConfig(), e.EVM.Context.BlockNumber) - p, found = precompiles[addr] - return p, found -} - -// ActivePrecompiles returns a list of all the active precompiled contract addresses -// for the current chain configuration. -func (EVM) ActivePrecompiles(rules params.Rules) []common.Address { - return vm.ActivePrecompiles(rules) -} - // RunPrecompiledContract runs a stateless precompiled contract and ignores the address and // value arguments. It uses the RunPrecompiledContract function from the geth vm package -func (e EVM) RunPrecompiledContract( +func (e *EVM) RunPrecompiledContract( p evm.StatefulPrecompiledContract, + caller vm.ContractRef, input []byte, suppliedGas uint64, + value *big.Int, + readOnly bool, ) (ret []byte, remainingGas uint64, err error) { - return vm.RunPrecompiledContract(p, input, suppliedGas) + return e.EVM.RunPrecompiledContract(p, caller, input, suppliedGas, value, readOnly) } diff --git a/x/evm/vm/interface.go b/x/evm/vm/interface.go index 77ef88c329..91c1aeae69 100644 --- a/x/evm/vm/interface.go +++ b/x/evm/vm/interface.go @@ -31,8 +31,6 @@ type PrecompiledContracts map[common.Address]vm.PrecompiledContract type PrecompiledContractCreator func( sdk.Context, ExtStateDB, - common.Address, - *big.Int, ) StatefulPrecompiledContract type StatefulPrecompiledContract interface { @@ -54,7 +52,7 @@ type EVM interface { Reset(txCtx vm.TxContext, statedb vm.StateDB) Cancel() Cancelled() bool //nolint - Interpreter() *vm.EVMInterpreter + Interpreter() vm.Interpreter Call(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) CallCode(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) DelegateCall(caller vm.ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) @@ -74,8 +72,12 @@ type EVM interface { Precompile(addr common.Address) (vm.PrecompiledContract, bool) RunPrecompiledContract( p StatefulPrecompiledContract, + caller vm.ContractRef, input []byte, - suppliedGas uint64) ( + suppliedGas uint64, + value *big.Int, + readOnly bool, + ) ( ret []byte, remainingGas uint64, err error, ) } From 13d1ec9df4962c01ce9d3c77047b3b0399752176 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Sat, 4 Feb 2023 22:30:37 +0800 Subject: [PATCH 11/41] sort contracts by asc --- x/evm/keeper/state_transition.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 8cc0e64202..fac9ab3b40 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -16,7 +16,9 @@ package keeper import ( + "bytes" "math/big" + "sort" tmtypes "github.com/tendermint/tendermint/types" @@ -82,6 +84,9 @@ func (k *Keeper) NewEVM( precompiles[addr] = c active = append(active, addr) } + sort.Slice(active, func(i, j int) bool { + return bytes.Compare(active[i].Bytes(), active[j].Bytes()) < 0 + }) evm := vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig) evm.WithPrecompiles(precompiles, active) return evm From 46b69f96d5ef33ff80d0759ca1726142cc3b992e Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 6 Feb 2023 09:21:57 +0800 Subject: [PATCH 12/41] replace with precompile list for more info: https://github.com/evmos/go-ethereum/blob/v1.10.26-evmos-rc1/core/vm/contracts.go#L204 https://github.com/evmos/go-ethereum/blob/v1.10.26-evmos-rc1/core/vm/contract.go#L29 --- app/app.go | 7 +++--- x/evm/keeper/keeper.go | 4 +-- x/evm/keeper/state_transition.go | 11 +++++---- x/evm/vm/geth/precompiles.go | 42 -------------------------------- x/evm/vm/interface.go | 4 +-- 5 files changed, 13 insertions(+), 55 deletions(-) delete mode 100644 x/evm/vm/geth/precompiles.go diff --git a/app/app.go b/app/app.go index b8d1eb9206..cbf74f4ee1 100644 --- a/app/app.go +++ b/app/app.go @@ -19,7 +19,6 @@ import ( "encoding/json" "fmt" "io" - "math/big" "net/http" "os" "path/filepath" @@ -134,7 +133,7 @@ import ( feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" // Force-load the tracer engines to trigger registration due to Go-Ethereum v1.10.15 changes - "github.com/ethereum/go-ethereum/common" + _ "github.com/ethereum/go-ethereum/eth/tracers/js" _ "github.com/ethereum/go-ethereum/eth/tracers/native" ) @@ -421,8 +420,8 @@ func NewEthermintApp( appCodec, authtypes.NewModuleAddress(govtypes.ModuleName), keys[feemarkettypes.StoreKey], tkeys[feemarkettypes.TransientKey], feeMarketSs, ) - contracts := map[common.Address]vm.PrecompiledContractCreator{ - common.BigToAddress(big.NewInt(100)): precompiles.NewBankContractCreator(app.BankKeeper), + contracts := []vm.PrecompiledContractCreator{ + precompiles.NewBankContractCreator(app.BankKeeper), } // Set authority to x/gov module account to only expect the module account to update params evmSs := app.GetSubspace(evmtypes.ModuleName) diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index aec7157c29..b7617638a7 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -72,7 +72,7 @@ type Keeper struct { hooks types.EvmHooks // custom precompiled smart contracts - customPrecompiles map[common.Address]evm.PrecompiledContractCreator + customPrecompiles []evm.PrecompiledContractCreator // evm constructor function evmConstructor evm.Constructor @@ -89,7 +89,7 @@ func NewKeeper( bankKeeper types.BankKeeper, sk types.StakingKeeper, fmk types.FeeMarketKeeper, - customPrecompiles map[common.Address]evm.PrecompiledContractCreator, + customPrecompiles []evm.PrecompiledContractCreator, evmConstructor evm.Constructor, tracer string, ss paramstypes.Subspace, diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index fac9ab3b40..5bdd687e17 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -53,7 +53,7 @@ func (k *Keeper) NewEVM( cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB, - contracts map[common.Address]evm.StatefulPrecompiledContract, + contracts []evm.StatefulPrecompiledContract, ) *vm.EVM { blockCtx := vm.BlockContext{ CanTransfer: core.CanTransfer, @@ -80,7 +80,8 @@ func (k *Keeper) NewEVM( precompiles[addr] = c active = append(active, addr) } - for addr, c := range contracts { + for _, c := range contracts { + addr := c.Address() precompiles[addr] = c active = append(active, addr) } @@ -349,10 +350,10 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, } // construct precompiles - contracts := make(map[common.Address]evm.StatefulPrecompiledContract, len(k.customPrecompiles)) + contracts := make([]evm.StatefulPrecompiledContract, len(k.customPrecompiles)) stateDB := k.StateDB(ctx, txConfig) - for addr, creator := range k.customPrecompiles { - contracts[addr] = creator(ctx, stateDB) + for i, creator := range k.customPrecompiles { + contracts[i] = creator(ctx, stateDB) } evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB, contracts) leftoverGas := msg.Gas() diff --git a/x/evm/vm/geth/precompiles.go b/x/evm/vm/geth/precompiles.go deleted file mode 100644 index 5226a3283d..0000000000 --- a/x/evm/vm/geth/precompiles.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2021 Evmos Foundation -// This file is part of Evmos' Ethermint library. -// -// The Ethermint library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The Ethermint library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE -package geth - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/params" - - evm "github.com/evmos/ethermint/x/evm/vm" -) - -// GetPrecompiles returns all the precompiled contracts defined given the -// current chain configuration and block height. -func GetPrecompiles(cfg *params.ChainConfig, blockNumber *big.Int) evm.PrecompiledContracts { - var precompiles evm.PrecompiledContracts - switch { - case cfg.IsBerlin(blockNumber): - precompiles = vm.PrecompiledContractsBerlin - case cfg.IsIstanbul(blockNumber): - precompiles = vm.PrecompiledContractsIstanbul - case cfg.IsByzantium(blockNumber): - precompiles = vm.PrecompiledContractsByzantium - default: - precompiles = vm.PrecompiledContractsHomestead - } - return precompiles -} diff --git a/x/evm/vm/interface.go b/x/evm/vm/interface.go index 91c1aeae69..33e3081a74 100644 --- a/x/evm/vm/interface.go +++ b/x/evm/vm/interface.go @@ -25,8 +25,8 @@ import ( "github.com/holiman/uint256" ) -// PrecompiledContracts defines a map of address -> precompiled contract -type PrecompiledContracts map[common.Address]vm.PrecompiledContract +// PrecompiledContracts defines list of precompiled contract +type PrecompiledContracts []vm.PrecompiledContract type PrecompiledContractCreator func( sdk.Context, From 6dfc48f30c42472856ce9336332efdf3dd120cf8 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 6 Feb 2023 09:48:03 +0800 Subject: [PATCH 13/41] align amt --- tests/integration_tests/hardhat/contracts/TestBank.sol | 8 ++++---- .../hardhat/contracts/TestBankDelegate.sol | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/integration_tests/hardhat/contracts/TestBank.sol b/tests/integration_tests/hardhat/contracts/TestBank.sol index d358f0afc9..7ca047bc7e 100644 --- a/tests/integration_tests/hardhat/contracts/TestBank.sol +++ b/tests/integration_tests/hardhat/contracts/TestBank.sol @@ -3,20 +3,20 @@ pragma solidity >0.6.6; contract TestBank { address constant bankContract = 0x0000000000000000000000000000000000000064; - function nativeMint(uint amount) public { + function nativeMint(uint256 amount) public { (bool result, bytes memory _data) = bankContract.call(abi.encodeWithSignature( "mint(address,uint256)", msg.sender, amount )); require(result, "native call"); } - function nativeBalanceOf(address addr) public returns (uint) { + function nativeBalanceOf(address addr) public returns (uint256) { (bool result, bytes memory data) = bankContract.call(abi.encodeWithSignature( "balanceOf(address,address)", address(this), addr )); require(result, "native call"); - return abi.decode(data, (uint)); + return abi.decode(data, (uint256)); } - function nativeMintRevert(uint amount) public { + function nativeMintRevert(uint256 amount) public { nativeMint(amount); revert("test"); } diff --git a/tests/integration_tests/hardhat/contracts/TestBankDelegate.sol b/tests/integration_tests/hardhat/contracts/TestBankDelegate.sol index 9410e8bf7f..cced84f3ab 100644 --- a/tests/integration_tests/hardhat/contracts/TestBankDelegate.sol +++ b/tests/integration_tests/hardhat/contracts/TestBankDelegate.sol @@ -2,20 +2,20 @@ pragma solidity >0.6.6; contract TestBankDelegate { - function nativeMint(address _contract, uint amount) public { + function nativeMint(address _contract, uint256 amount) public { (bool result, bytes memory _data) = _contract.delegatecall(abi.encodeWithSignature( "nativeMint(uint256)", amount )); require(result, "native call"); } - function nativeBalanceOf(address _contract, address addr) public returns (uint) { + function nativeBalanceOf(address _contract, address addr) public returns (uint256) { (bool result, bytes memory data) = _contract.delegatecall(abi.encodeWithSignature( "nativeBalanceOf(address)", addr )); require(result, "native call"); - return abi.decode(data, (uint)); + return abi.decode(data, (uint256)); } - function nativeMintRevert(address _contract, uint amount) public { + function nativeMintRevert(address _contract, uint256 amount) public { nativeMint(_contract, amount); revert("test"); } From 96ea97fe821700c271474a6a774eb17730741e16 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 6 Feb 2023 09:23:11 +0800 Subject: [PATCH 14/41] test recursive transfer --- app/app.go | 1 + .../hardhat/contracts/TestTransfer.sol | 33 ++++++ tests/integration_tests/test_precompiles.py | 61 +++++++++- tests/integration_tests/utils.py | 5 +- x/evm/keeper/precompiles/transfer.go | 104 ++++++++++++++++++ 5 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 tests/integration_tests/hardhat/contracts/TestTransfer.sol create mode 100644 x/evm/keeper/precompiles/transfer.go diff --git a/app/app.go b/app/app.go index cbf74f4ee1..68c5e41119 100644 --- a/app/app.go +++ b/app/app.go @@ -422,6 +422,7 @@ func NewEthermintApp( ) contracts := []vm.PrecompiledContractCreator{ precompiles.NewBankContractCreator(app.BankKeeper), + precompiles.NewTransferContractCreator(app.BankKeeper), } // Set authority to x/gov module account to only expect the module account to update params evmSs := app.GetSubspace(evmtypes.ModuleName) diff --git a/tests/integration_tests/hardhat/contracts/TestTransfer.sol b/tests/integration_tests/hardhat/contracts/TestTransfer.sol new file mode 100644 index 0000000000..c996e949f8 --- /dev/null +++ b/tests/integration_tests/hardhat/contracts/TestTransfer.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TestTransfer { + address constant recursiveContract = 0x0000000000000000000000000000000000000066; + event Result(bool indexed result); + struct Params { + address receiver; + uint256 amount; + } + constructor() payable {} + + function nativeTransfer(Params memory params) public { + (bool result, ) = recursiveContract.call(abi.encodeWithSignature( + "nativeTransfer(address,uint256)", params.receiver, params.amount + )); + require(result, "native transfer"); + } + + function recursiveTransfer(Params[] memory params) public { + nativeTransfer(params[0]); + nativeTransfer(params[1]); + + Params[] memory _params = new Params[](params.length - 2); + for (uint i = 2; i < params.length; i++) { + _params[i - 2] = params[i]; + } + (bool result, ) = address(this).call(abi.encodeWithSignature( + "recursiveTransfer((address,uint256)[])", _params + )); + emit Result(result); + } +} \ No newline at end of file diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index b439da645d..d679c85458 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -1,4 +1,21 @@ -from .utils import ADDRS, CONTRACTS, deploy_contract, eth_to_bech32, send_transaction +from typing import NamedTuple + +from eth_typing import HexAddress +from eth_utils import abi +from hexbytes import HexBytes + +from .utils import ( + ADDRS, + CONTRACTS, + KEYS, + deploy_contract, + eth_to_bech32, + send_transaction, +) + + +def get_balance(cli, addr, denom): + return cli.balance(eth_to_bech32(addr), denom) def test_call(ethermint): @@ -14,7 +31,8 @@ def test_call(ethermint): assert contract.caller.nativeBalanceOf(addr) == amount # query balance through cosmos rpc cli = ethermint.cosmos_cli() - assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount + denom = "evm/" + contract.address + assert get_balance(cli, addr, denom) == amount # test exception revert tx = contract.functions.nativeMintRevert(amount).build_transaction( @@ -27,7 +45,7 @@ def test_call(ethermint): assert contract.caller.nativeBalanceOf(addr) == amount # query balance through cosmos rpc cli = ethermint.cosmos_cli() - assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount + assert get_balance(cli, addr, denom) == amount def test_delegate(ethermint): @@ -46,7 +64,8 @@ def test_delegate(ethermint): assert contract.caller.nativeBalanceOf(bank, addr) == amount # query balance through cosmos rpc cli = ethermint.cosmos_cli() - assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount + denom = "evm/" + contract.address + assert get_balance(cli, addr, denom) == amount # test exception revert tx = contract.functions.nativeMintRevert(bank, amount).build_transaction( @@ -59,4 +78,36 @@ def test_delegate(ethermint): assert contract.caller.nativeBalanceOf(bank, addr) == amount # query balance through cosmos rpc cli = ethermint.cosmos_cli() - assert cli.balance(eth_to_bech32(addr), "evm/" + contract.address) == amount + assert get_balance(cli, addr, denom) == amount + + +class Params(NamedTuple): + address: HexAddress + amount: int + + +def test_transfer(ethermint): + w3 = ethermint.w3 + amount = 500000000000000000000 + name = CONTRACTS["TestTransfer"] + contract, res = deploy_contract(w3, name, (), KEYS["validator"], amount) + contract_address = res["contractAddress"] + denom = "aphoton" + addr = ADDRS["signer2"] + recipient = ADDRS["signer1"] + cli = ethermint.cosmos_cli() + recipient_balance = get_balance(cli, recipient, denom) + contract_balance = get_balance(cli, contract_address, denom) + success = [1000, 20000] + fail = [300000, amount * 2] + params = [Params(recipient, val) for val in (success + fail)] + tx = contract.functions.recursiveTransfer(params).build_transaction({"from": addr}) + receipt = send_transaction(w3, tx) + assert receipt.status == 1 + assert receipt.logs[0]["topics"] == [ + HexBytes(abi.event_signature_to_log_topic("Result(bool)")), + HexBytes(b"\x00" * 32), + ] + expect_diff = sum(success) + assert recipient_balance + expect_diff == get_balance(cli, recipient, denom) + assert contract_balance - expect_diff == get_balance(cli, contract_address, denom) diff --git a/tests/integration_tests/utils.py b/tests/integration_tests/utils.py index 17e6959495..87e61c2ee4 100644 --- a/tests/integration_tests/utils.py +++ b/tests/integration_tests/utils.py @@ -34,6 +34,7 @@ "StateContract": "StateContract.sol", "TestBank": "TestBank.sol", "TestBankDelegate": "TestBankDelegate.sol", + "TestTransfer": "TestTransfer.sol", } @@ -126,7 +127,7 @@ def wait_for_block_time(cli, t): time.sleep(0.5) -def deploy_contract(w3, jsonfile, args=(), key=KEYS["validator"]): +def deploy_contract(w3, jsonfile, args=(), key=KEYS["validator"], value=None): """ deploy contract and return the deployed contract instance """ @@ -134,6 +135,8 @@ def deploy_contract(w3, jsonfile, args=(), key=KEYS["validator"]): info = json.loads(jsonfile.read_text()) contract = w3.eth.contract(abi=info["abi"], bytecode=info["bytecode"]) tx = contract.constructor(*args).build_transaction({"from": acct.address}) + if value: + tx["value"] = value txreceipt = send_transaction(w3, tx, key) assert txreceipt.status == 1 address = txreceipt.contractAddress diff --git a/x/evm/keeper/precompiles/transfer.go b/x/evm/keeper/precompiles/transfer.go new file mode 100644 index 0000000000..a6fdcf82e4 --- /dev/null +++ b/x/evm/keeper/precompiles/transfer.go @@ -0,0 +1,104 @@ +package precompiles + +import ( + "errors" + "math/big" + + sdkmath "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/evmos/ethermint/x/evm/types" + evm "github.com/evmos/ethermint/x/evm/vm" +) + +var ( + NativeTransferMethod abi.Method + TransferTransferMethod abi.Method + + _ evm.StatefulPrecompiledContract = (*TransferContract)(nil) +) + +func init() { + addressType, _ := abi.NewType("address", "", nil) + uint256Type, _ := abi.NewType("uint256", "", nil) + NativeTransferMethod = abi.NewMethod( + "nativeTransfer", "nativeTransfer", abi.Function, "", false, false, abi.Arguments{abi.Argument{ + Name: "receiver", + Type: addressType, + }, abi.Argument{ + Name: "amount", + Type: uint256Type, + }}, + nil, + ) +} + +type TransferContract struct { + ctx sdk.Context + bankKeeper types.BankKeeper + stateDB evm.ExtStateDB +} + +// NewTransferContractCreator creates the precompiled contract to manage native tokens +func NewTransferContractCreator(bankKeeper types.BankKeeper) evm.PrecompiledContractCreator { + return func( + ctx sdk.Context, + stateDB evm.ExtStateDB, + ) evm.StatefulPrecompiledContract { + return &TransferContract{ + ctx: ctx, + bankKeeper: bankKeeper, + stateDB: stateDB, + } + } +} + +func (tc *TransferContract) Address() common.Address { + return common.BytesToAddress([]byte{102}) +} + +// RequiredGas calculates the contract gas use +func (tc *TransferContract) RequiredGas(input []byte) uint64 { + // TODO estimate required gas + return 0 +} + +func (tc *TransferContract) IsStateful() bool { + return true +} + +func (tc *TransferContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { + // parse input + methodID := contract.Input[:4] + switch string(methodID) { + case string(NativeTransferMethod.ID): + if readonly { + return nil, errors.New("the method is not readonly") + } + args, err := NativeTransferMethod.Inputs.Unpack(contract.Input[4:]) + if err != nil { + return nil, errors.New("fail to unpack input arguments") + } + recipient := args[0].(common.Address) + amount := args[1].(*big.Int) + if amount.Sign() <= 0 { + return nil, errors.New("invalid amount") + } + err = tc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { + from := sdk.AccAddress(contract.CallerAddress.Bytes()) + to := sdk.AccAddress(recipient.Bytes()) + amt := sdk.NewCoins(sdk.NewCoin(types.DefaultEVMDenom, sdkmath.NewIntFromBigInt(amount))) + if err := tc.bankKeeper.SendCoins(ctx, from, to, amt); err != nil { + return sdkerrors.Wrap(err, "fail to send coins in precompiled contract") + } + return nil + }) + return nil, err + default: + return nil, errors.New("unknown method") + } +} From e73d46d3e79360623b0fb58c9286732fd24cb2f0 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 6 Feb 2023 09:53:55 +0800 Subject: [PATCH 15/41] test nested call --- .../hardhat/contracts/TestBankCaller.sol | 20 +++++++++++++++++++ tests/integration_tests/test_precompiles.py | 15 ++++++++++++++ tests/integration_tests/utils.py | 1 + 3 files changed, 36 insertions(+) create mode 100644 tests/integration_tests/hardhat/contracts/TestBankCaller.sol diff --git a/tests/integration_tests/hardhat/contracts/TestBankCaller.sol b/tests/integration_tests/hardhat/contracts/TestBankCaller.sol new file mode 100644 index 0000000000..ca1d9b3e2e --- /dev/null +++ b/tests/integration_tests/hardhat/contracts/TestBankCaller.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >0.6.6; + +contract TestBankCaller { + uint256 state; + + function mint(address callee, uint amount) public { + (bool success, bytes memory data) = callee.call(abi.encodeWithSignature( + "nativeMintRevert(uint256)", amount + )); + if (!success) { + // ignore the error and move on + state++; + } + } + + function getLastState() public view returns (uint256) { + return state; + } +} diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index d679c85458..6cdbde0483 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -81,6 +81,21 @@ def test_delegate(ethermint): assert get_balance(cli, addr, denom) == amount +def test_nested(ethermint): + w3 = ethermint.w3 + addr = ADDRS["validator"] + amount = 100 + _, res = deploy_contract(w3, CONTRACTS["TestBank"]) + contract, _ = deploy_contract(w3, CONTRACTS["TestBankCaller"]) + data = {"from": addr} + tx = contract.functions.mint(res["contractAddress"], amount).build_transaction(data) + receipt = send_transaction(w3, tx) + assert receipt.status == 1, "expect success" + assert contract.caller.getLastState() == 1 + denom = "evm/" + contract.address + assert get_balance(ethermint.cosmos_cli(), addr, denom) == 0 + + class Params(NamedTuple): address: HexAddress amount: int diff --git a/tests/integration_tests/utils.py b/tests/integration_tests/utils.py index 87e61c2ee4..ec3f5e5bb9 100644 --- a/tests/integration_tests/utils.py +++ b/tests/integration_tests/utils.py @@ -35,6 +35,7 @@ "TestBank": "TestBank.sol", "TestBankDelegate": "TestBankDelegate.sol", "TestTransfer": "TestTransfer.sol", + "TestBankCaller": "TestBankCaller.sol", } From df95629322efd657a9aa3f053e230c6c970ec01e Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 7 Feb 2023 13:01:35 +0800 Subject: [PATCH 16/41] clean up vm related stuff --- app/app.go | 9 +-- x/evm/keeper/keeper.go | 30 +++------ x/evm/keeper/precompiles/bank.go | 20 ++---- x/evm/keeper/precompiles/interface.go | 18 +++++ x/evm/keeper/precompiles/transfer.go | 19 +----- x/evm/keeper/state_transition.go | 26 ++++---- x/evm/vm/geth/geth.go | 79 ---------------------- x/evm/vm/interface.go | 94 --------------------------- 8 files changed, 47 insertions(+), 248 deletions(-) create mode 100644 x/evm/keeper/precompiles/interface.go delete mode 100644 x/evm/vm/geth/geth.go delete mode 100644 x/evm/vm/interface.go diff --git a/app/app.go b/app/app.go index 68c5e41119..5dc1c21b7c 100644 --- a/app/app.go +++ b/app/app.go @@ -124,10 +124,7 @@ import ( ethermint "github.com/evmos/ethermint/types" "github.com/evmos/ethermint/x/evm" evmkeeper "github.com/evmos/ethermint/x/evm/keeper" - "github.com/evmos/ethermint/x/evm/keeper/precompiles" evmtypes "github.com/evmos/ethermint/x/evm/types" - "github.com/evmos/ethermint/x/evm/vm" - "github.com/evmos/ethermint/x/evm/vm/geth" "github.com/evmos/ethermint/x/feemarket" feemarketkeeper "github.com/evmos/ethermint/x/feemarket/keeper" feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" @@ -420,16 +417,12 @@ func NewEthermintApp( appCodec, authtypes.NewModuleAddress(govtypes.ModuleName), keys[feemarkettypes.StoreKey], tkeys[feemarkettypes.TransientKey], feeMarketSs, ) - contracts := []vm.PrecompiledContractCreator{ - precompiles.NewBankContractCreator(app.BankKeeper), - precompiles.NewTransferContractCreator(app.BankKeeper), - } // Set authority to x/gov module account to only expect the module account to update params evmSs := app.GetSubspace(evmtypes.ModuleName) app.EvmKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.FeeMarketKeeper, - contracts, geth.NewEVM, tracer, evmSs, + tracer, evmSs, ) // Create IBC Keeper diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index b7617638a7..b424fbda3d 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -34,7 +34,6 @@ import ( ethermint "github.com/evmos/ethermint/types" "github.com/evmos/ethermint/x/evm/statedb" "github.com/evmos/ethermint/x/evm/types" - evm "github.com/evmos/ethermint/x/evm/vm" ) // Keeper grants access to the EVM module state and implements the go-ethereum StateDB interface. @@ -71,11 +70,6 @@ type Keeper struct { // EVM Hooks for tx post-processing hooks types.EvmHooks - // custom precompiled smart contracts - customPrecompiles []evm.PrecompiledContractCreator - - // evm constructor function - evmConstructor evm.Constructor // Legacy subspace ss paramstypes.Subspace } @@ -89,8 +83,6 @@ func NewKeeper( bankKeeper types.BankKeeper, sk types.StakingKeeper, fmk types.FeeMarketKeeper, - customPrecompiles []evm.PrecompiledContractCreator, - evmConstructor evm.Constructor, tracer string, ss paramstypes.Subspace, ) *Keeper { @@ -106,18 +98,16 @@ func NewKeeper( // NOTE: we pass in the parameter space to the CommitStateDB in order to use custom denominations for the EVM operations return &Keeper{ - cdc: cdc, - authority: authority, - accountKeeper: ak, - bankKeeper: bankKeeper, - stakingKeeper: sk, - feeMarketKeeper: fmk, - storeKey: storeKey, - transientKey: transientKey, - customPrecompiles: customPrecompiles, - evmConstructor: evmConstructor, - tracer: tracer, - ss: ss, + cdc: cdc, + authority: authority, + accountKeeper: ak, + bankKeeper: bankKeeper, + stakingKeeper: sk, + feeMarketKeeper: fmk, + storeKey: storeKey, + transientKey: transientKey, + tracer: tracer, + ss: ss, } } diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index 5f5ea1eaf0..54e03cd5a3 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -12,7 +12,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/evmos/ethermint/x/evm/types" - evm "github.com/evmos/ethermint/x/evm/vm" ) const EVMDenomPrefix = "evm/" @@ -20,8 +19,6 @@ const EVMDenomPrefix = "evm/" var ( MintMethod abi.Method BalanceOfMethod abi.Method - - _ evm.StatefulPrecompiledContract = (*BankContract)(nil) ) func init() { @@ -59,21 +56,12 @@ func EVMDenom(token common.Address) string { type BankContract struct { ctx sdk.Context bankKeeper types.BankKeeper - stateDB evm.ExtStateDB + stateDB ExtStateDB } -// NewBankContractCreator creates the precompiled contract to manage native tokens -func NewBankContractCreator(bankKeeper types.BankKeeper) evm.PrecompiledContractCreator { - return func( - ctx sdk.Context, - stateDB evm.ExtStateDB, - ) evm.StatefulPrecompiledContract { - return &BankContract{ - ctx: ctx, - bankKeeper: bankKeeper, - stateDB: stateDB, - } - } +// NewBankContract creates the precompiled contract to manage native tokens +func NewBankContract(ctx sdk.Context, bankKeeper types.BankKeeper, stateDB ExtStateDB) StatefulPrecompiledContract { + return &BankContract{ctx, bankKeeper, stateDB} } func (bc *BankContract) Address() common.Address { diff --git a/x/evm/keeper/precompiles/interface.go b/x/evm/keeper/precompiles/interface.go new file mode 100644 index 0000000000..6b2b427e21 --- /dev/null +++ b/x/evm/keeper/precompiles/interface.go @@ -0,0 +1,18 @@ +package precompiles + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +// PrecompiledContracts defines list of precompiled contract +type PrecompiledContracts []vm.PrecompiledContract + +type StatefulPrecompiledContract interface { + vm.PrecompiledContract +} + +// ExtStateDB defines extra methods of statedb to support stateful precompiled contracts +type ExtStateDB interface { + ExecuteNativeAction(action func(ctx sdk.Context) error) error +} diff --git a/x/evm/keeper/precompiles/transfer.go b/x/evm/keeper/precompiles/transfer.go index a6fdcf82e4..0d08cd7d42 100644 --- a/x/evm/keeper/precompiles/transfer.go +++ b/x/evm/keeper/precompiles/transfer.go @@ -12,14 +12,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/evmos/ethermint/x/evm/types" - evm "github.com/evmos/ethermint/x/evm/vm" ) var ( NativeTransferMethod abi.Method TransferTransferMethod abi.Method - - _ evm.StatefulPrecompiledContract = (*TransferContract)(nil) ) func init() { @@ -40,21 +37,11 @@ func init() { type TransferContract struct { ctx sdk.Context bankKeeper types.BankKeeper - stateDB evm.ExtStateDB + stateDB ExtStateDB } -// NewTransferContractCreator creates the precompiled contract to manage native tokens -func NewTransferContractCreator(bankKeeper types.BankKeeper) evm.PrecompiledContractCreator { - return func( - ctx sdk.Context, - stateDB evm.ExtStateDB, - ) evm.StatefulPrecompiledContract { - return &TransferContract{ - ctx: ctx, - bankKeeper: bankKeeper, - stateDB: stateDB, - } - } +func NewTransferContract(ctx sdk.Context, bankKeeper types.BankKeeper, stateDB ExtStateDB) StatefulPrecompiledContract { + return &TransferContract{ctx, bankKeeper, stateDB} } func (tc *TransferContract) Address() common.Address { diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 5bdd687e17..5b026801a7 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -26,6 +26,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ethermint "github.com/evmos/ethermint/types" + "github.com/evmos/ethermint/x/evm/keeper/precompiles" "github.com/evmos/ethermint/x/evm/statedb" "github.com/evmos/ethermint/x/evm/types" @@ -35,7 +36,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" - evm "github.com/evmos/ethermint/x/evm/vm" ) // NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters @@ -53,7 +53,6 @@ func (k *Keeper) NewEVM( cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB, - contracts []evm.StatefulPrecompiledContract, ) *vm.EVM { blockCtx := vm.BlockContext{ CanTransfer: core.CanTransfer, @@ -67,29 +66,32 @@ func (k *Keeper) NewEVM( BaseFee: cfg.BaseFee, Random: nil, // not supported } - txCtx := core.NewEVMTxContext(msg) if tracer == nil { tracer = k.Tracer(ctx, msg, cfg.ChainConfig) } vmConfig := k.VMConfig(ctx, msg, cfg, tracer) rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil) - precompiles := make(map[common.Address]vm.PrecompiledContract) + contracts := make(map[common.Address]vm.PrecompiledContract) active := make([]common.Address, 0) for addr, c := range vm.DefaultPrecompiles(rules) { - precompiles[addr] = c + contracts[addr] = c active = append(active, addr) } - for _, c := range contracts { + customContracts := []precompiles.StatefulPrecompiledContract{ + precompiles.NewBankContract(ctx, k.bankKeeper, stateDB.(precompiles.ExtStateDB)), + precompiles.NewTransferContract(ctx, k.bankKeeper, stateDB.(precompiles.ExtStateDB)), + } + for _, c := range customContracts { addr := c.Address() - precompiles[addr] = c + contracts[addr] = c active = append(active, addr) } sort.Slice(active, func(i, j int) bool { return bytes.Compare(active[i].Bytes(), active[j].Bytes()) < 0 }) evm := vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig) - evm.WithPrecompiles(precompiles, active) + evm.WithPrecompiles(contracts, active) return evm } @@ -348,14 +350,8 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, } else if !cfg.Params.EnableCall && msg.To() != nil { return nil, errorsmod.Wrap(types.ErrCallDisabled, "failed to call contract") } - - // construct precompiles - contracts := make([]evm.StatefulPrecompiledContract, len(k.customPrecompiles)) stateDB := k.StateDB(ctx, txConfig) - for i, creator := range k.customPrecompiles { - contracts[i] = creator(ctx, stateDB) - } - evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB, contracts) + evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB) leftoverGas := msg.Gas() // Allow the tracer captures the tx level events, mainly the gas consumption. vmCfg := evm.Config diff --git a/x/evm/vm/geth/geth.go b/x/evm/vm/geth/geth.go deleted file mode 100644 index ff6b4fc9c1..0000000000 --- a/x/evm/vm/geth/geth.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2021 Evmos Foundation -// This file is part of Evmos' Ethermint library. -// -// The Ethermint library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The Ethermint library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE -package geth - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/params" - - evm "github.com/evmos/ethermint/x/evm/vm" -) - -var ( - _ evm.EVM = (*EVM)(nil) - _ evm.Constructor = NewEVM -) - -// EVM is the wrapper for the go-ethereum EVM. -type EVM struct { - *vm.EVM -} - -// NewEVM defines the constructor function for the go-ethereum (geth) EVM. It uses -// the default precompiled contracts and the EVM concrete implementation from -// geth. -func NewEVM( - blockCtx vm.BlockContext, - txCtx vm.TxContext, - stateDB vm.StateDB, - chainConfig *params.ChainConfig, - config vm.Config, - _ evm.PrecompiledContracts, // unused -) evm.EVM { - return &EVM{ - EVM: vm.NewEVM(blockCtx, txCtx, stateDB, chainConfig, config), - } -} - -// Context returns the EVM's Block Context -func (e EVM) Context() vm.BlockContext { - return e.EVM.Context -} - -// TxContext returns the EVM's Tx Context -func (e EVM) TxContext() vm.TxContext { - return e.EVM.TxContext -} - -// Config returns the configuration options for the EVM. -func (e EVM) Config() vm.Config { - return e.EVM.Config -} - -// RunPrecompiledContract runs a stateless precompiled contract and ignores the address and -// value arguments. It uses the RunPrecompiledContract function from the geth vm package -func (e *EVM) RunPrecompiledContract( - p evm.StatefulPrecompiledContract, - caller vm.ContractRef, - input []byte, - suppliedGas uint64, - value *big.Int, - readOnly bool, -) (ret []byte, remainingGas uint64, err error) { - return e.EVM.RunPrecompiledContract(p, caller, input, suppliedGas, value, readOnly) -} diff --git a/x/evm/vm/interface.go b/x/evm/vm/interface.go deleted file mode 100644 index 33e3081a74..0000000000 --- a/x/evm/vm/interface.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2021 Evmos Foundation -// This file is part of Evmos' Ethermint library. -// -// The Ethermint library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The Ethermint library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE -package vm - -import ( - "math/big" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/params" - "github.com/holiman/uint256" -) - -// PrecompiledContracts defines list of precompiled contract -type PrecompiledContracts []vm.PrecompiledContract - -type PrecompiledContractCreator func( - sdk.Context, - ExtStateDB, -) StatefulPrecompiledContract - -type StatefulPrecompiledContract interface { - vm.PrecompiledContract - // RunStateful(evm EVM, addr common.Address, input []byte, value *big.Int) (ret []byte, err error) -} - -// ExtStateDB defines extra methods of statedb to support stateful precompiled contracts -type ExtStateDB interface { - ExecuteNativeAction(action func(ctx sdk.Context) error) error -} - -// EVM defines the interface for the Ethereum Virtual Machine used by the EVM module. -type EVM interface { - Config() vm.Config - Context() vm.BlockContext - TxContext() vm.TxContext - - Reset(txCtx vm.TxContext, statedb vm.StateDB) - Cancel() - Cancelled() bool //nolint - Interpreter() vm.Interpreter - Call(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) - CallCode(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) - DelegateCall(caller vm.ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) - StaticCall(caller vm.ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) - Create(caller vm.ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) - Create2( - caller vm.ContractRef, - code []byte, - gas uint64, - endowment *big.Int, - salt *uint256.Int) ( - ret []byte, contractAddr common.Address, leftOverGas uint64, err error, - ) - ChainConfig() *params.ChainConfig - - ActivePrecompiles(rules params.Rules) []common.Address - Precompile(addr common.Address) (vm.PrecompiledContract, bool) - RunPrecompiledContract( - p StatefulPrecompiledContract, - caller vm.ContractRef, - input []byte, - suppliedGas uint64, - value *big.Int, - readOnly bool, - ) ( - ret []byte, remainingGas uint64, err error, - ) -} - -// Constructor defines the function used to instantiate the EVM on -// each state transition. -type Constructor func( - blockCtx vm.BlockContext, - txCtx vm.TxContext, - stateDB vm.StateDB, - chainConfig *params.ChainConfig, - config vm.Config, - customPrecompiles PrecompiledContracts, -) EVM From e43465f3e8bf097022ee92bde26944889391f20b Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 7 Feb 2023 17:41:00 +0800 Subject: [PATCH 17/41] clean up --- x/evm/keeper/precompiles/interface.go | 3 --- x/evm/statedb/types.go | 12 ------------ 2 files changed, 15 deletions(-) delete mode 100644 x/evm/statedb/types.go diff --git a/x/evm/keeper/precompiles/interface.go b/x/evm/keeper/precompiles/interface.go index 6b2b427e21..b079043853 100644 --- a/x/evm/keeper/precompiles/interface.go +++ b/x/evm/keeper/precompiles/interface.go @@ -5,9 +5,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" ) -// PrecompiledContracts defines list of precompiled contract -type PrecompiledContracts []vm.PrecompiledContract - type StatefulPrecompiledContract interface { vm.PrecompiledContract } diff --git a/x/evm/statedb/types.go b/x/evm/statedb/types.go deleted file mode 100644 index a580972647..0000000000 --- a/x/evm/statedb/types.go +++ /dev/null @@ -1,12 +0,0 @@ -package statedb - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// ExtState manage some extended states which needs to be committed together with StateDB. -// mainly for the stateful precompiled contracts. -type ExtState interface { - // write the dirty states to cosmos-sdk storage - Commit(sdk.Context) error -} From 918dc3ccf3a340448220b72893d99c2221e6558c Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 7 Feb 2023 17:43:47 +0800 Subject: [PATCH 18/41] rm test transfer --- .../hardhat/contracts/TestTransfer.sol | 33 ------- tests/integration_tests/test_precompiles.py | 39 -------- tests/integration_tests/utils.py | 1 - x/evm/keeper/precompiles/transfer.go | 91 ------------------- x/evm/keeper/state_transition.go | 1 - 5 files changed, 165 deletions(-) delete mode 100644 tests/integration_tests/hardhat/contracts/TestTransfer.sol delete mode 100644 x/evm/keeper/precompiles/transfer.go diff --git a/tests/integration_tests/hardhat/contracts/TestTransfer.sol b/tests/integration_tests/hardhat/contracts/TestTransfer.sol deleted file mode 100644 index c996e949f8..0000000000 --- a/tests/integration_tests/hardhat/contracts/TestTransfer.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -contract TestTransfer { - address constant recursiveContract = 0x0000000000000000000000000000000000000066; - event Result(bool indexed result); - struct Params { - address receiver; - uint256 amount; - } - constructor() payable {} - - function nativeTransfer(Params memory params) public { - (bool result, ) = recursiveContract.call(abi.encodeWithSignature( - "nativeTransfer(address,uint256)", params.receiver, params.amount - )); - require(result, "native transfer"); - } - - function recursiveTransfer(Params[] memory params) public { - nativeTransfer(params[0]); - nativeTransfer(params[1]); - - Params[] memory _params = new Params[](params.length - 2); - for (uint i = 2; i < params.length; i++) { - _params[i - 2] = params[i]; - } - (bool result, ) = address(this).call(abi.encodeWithSignature( - "recursiveTransfer((address,uint256)[])", _params - )); - emit Result(result); - } -} \ No newline at end of file diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index 6cdbde0483..fe32797ea0 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -1,13 +1,6 @@ -from typing import NamedTuple - -from eth_typing import HexAddress -from eth_utils import abi -from hexbytes import HexBytes - from .utils import ( ADDRS, CONTRACTS, - KEYS, deploy_contract, eth_to_bech32, send_transaction, @@ -94,35 +87,3 @@ def test_nested(ethermint): assert contract.caller.getLastState() == 1 denom = "evm/" + contract.address assert get_balance(ethermint.cosmos_cli(), addr, denom) == 0 - - -class Params(NamedTuple): - address: HexAddress - amount: int - - -def test_transfer(ethermint): - w3 = ethermint.w3 - amount = 500000000000000000000 - name = CONTRACTS["TestTransfer"] - contract, res = deploy_contract(w3, name, (), KEYS["validator"], amount) - contract_address = res["contractAddress"] - denom = "aphoton" - addr = ADDRS["signer2"] - recipient = ADDRS["signer1"] - cli = ethermint.cosmos_cli() - recipient_balance = get_balance(cli, recipient, denom) - contract_balance = get_balance(cli, contract_address, denom) - success = [1000, 20000] - fail = [300000, amount * 2] - params = [Params(recipient, val) for val in (success + fail)] - tx = contract.functions.recursiveTransfer(params).build_transaction({"from": addr}) - receipt = send_transaction(w3, tx) - assert receipt.status == 1 - assert receipt.logs[0]["topics"] == [ - HexBytes(abi.event_signature_to_log_topic("Result(bool)")), - HexBytes(b"\x00" * 32), - ] - expect_diff = sum(success) - assert recipient_balance + expect_diff == get_balance(cli, recipient, denom) - assert contract_balance - expect_diff == get_balance(cli, contract_address, denom) diff --git a/tests/integration_tests/utils.py b/tests/integration_tests/utils.py index ec3f5e5bb9..5eb466b5de 100644 --- a/tests/integration_tests/utils.py +++ b/tests/integration_tests/utils.py @@ -34,7 +34,6 @@ "StateContract": "StateContract.sol", "TestBank": "TestBank.sol", "TestBankDelegate": "TestBankDelegate.sol", - "TestTransfer": "TestTransfer.sol", "TestBankCaller": "TestBankCaller.sol", } diff --git a/x/evm/keeper/precompiles/transfer.go b/x/evm/keeper/precompiles/transfer.go deleted file mode 100644 index 0d08cd7d42..0000000000 --- a/x/evm/keeper/precompiles/transfer.go +++ /dev/null @@ -1,91 +0,0 @@ -package precompiles - -import ( - "errors" - "math/big" - - sdkmath "cosmossdk.io/math" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/evmos/ethermint/x/evm/types" -) - -var ( - NativeTransferMethod abi.Method - TransferTransferMethod abi.Method -) - -func init() { - addressType, _ := abi.NewType("address", "", nil) - uint256Type, _ := abi.NewType("uint256", "", nil) - NativeTransferMethod = abi.NewMethod( - "nativeTransfer", "nativeTransfer", abi.Function, "", false, false, abi.Arguments{abi.Argument{ - Name: "receiver", - Type: addressType, - }, abi.Argument{ - Name: "amount", - Type: uint256Type, - }}, - nil, - ) -} - -type TransferContract struct { - ctx sdk.Context - bankKeeper types.BankKeeper - stateDB ExtStateDB -} - -func NewTransferContract(ctx sdk.Context, bankKeeper types.BankKeeper, stateDB ExtStateDB) StatefulPrecompiledContract { - return &TransferContract{ctx, bankKeeper, stateDB} -} - -func (tc *TransferContract) Address() common.Address { - return common.BytesToAddress([]byte{102}) -} - -// RequiredGas calculates the contract gas use -func (tc *TransferContract) RequiredGas(input []byte) uint64 { - // TODO estimate required gas - return 0 -} - -func (tc *TransferContract) IsStateful() bool { - return true -} - -func (tc *TransferContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { - // parse input - methodID := contract.Input[:4] - switch string(methodID) { - case string(NativeTransferMethod.ID): - if readonly { - return nil, errors.New("the method is not readonly") - } - args, err := NativeTransferMethod.Inputs.Unpack(contract.Input[4:]) - if err != nil { - return nil, errors.New("fail to unpack input arguments") - } - recipient := args[0].(common.Address) - amount := args[1].(*big.Int) - if amount.Sign() <= 0 { - return nil, errors.New("invalid amount") - } - err = tc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { - from := sdk.AccAddress(contract.CallerAddress.Bytes()) - to := sdk.AccAddress(recipient.Bytes()) - amt := sdk.NewCoins(sdk.NewCoin(types.DefaultEVMDenom, sdkmath.NewIntFromBigInt(amount))) - if err := tc.bankKeeper.SendCoins(ctx, from, to, amt); err != nil { - return sdkerrors.Wrap(err, "fail to send coins in precompiled contract") - } - return nil - }) - return nil, err - default: - return nil, errors.New("unknown method") - } -} diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 5b026801a7..c6a07da5df 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -80,7 +80,6 @@ func (k *Keeper) NewEVM( } customContracts := []precompiles.StatefulPrecompiledContract{ precompiles.NewBankContract(ctx, k.bankKeeper, stateDB.(precompiles.ExtStateDB)), - precompiles.NewTransferContract(ctx, k.bankKeeper, stateDB.(precompiles.ExtStateDB)), } for _, c := range customContracts { addr := c.Address() From 4c0ee05dc8b8ab576fc89bd3b68b41080ace9641 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 7 Feb 2023 17:44:09 +0800 Subject: [PATCH 19/41] use stable sort --- x/evm/keeper/state_transition.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index c6a07da5df..7cb83ed959 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -86,7 +86,7 @@ func (k *Keeper) NewEVM( contracts[addr] = c active = append(active, addr) } - sort.Slice(active, func(i, j int) bool { + sort.SliceStable(active, func(i, j int) bool { return bytes.Compare(active[i].Bytes(), active[j].Bytes()) < 0 }) evm := vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig) From 84059d713b2723f1ea669ff82aad08df7455757a Mon Sep 17 00:00:00 2001 From: HuangYi Date: Tue, 10 Jan 2023 22:38:08 +0800 Subject: [PATCH 20/41] demonstrate use new cache store clone to integrate native stateful precompiles --- x/evm/keeper/state_transition.go | 5 ++++- x/evm/statedb/native.go | 20 ++++++++++++++++++++ x/evm/statedb/statedb.go | 23 +++++++++++++++++++---- 3 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 x/evm/statedb/native.go diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index ad90dba5fd..6b98d3c97e 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -400,9 +400,12 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, // The dirty states in `StateDB` is either committed or discarded after return if commit { - if err := stateDB.Commit(); err != nil { + dbCtx, err := stateDB.Commit() + if err != nil { return nil, errorsmod.Wrap(err, "failed to commit stateDB") } + + ctx.MultiStore().Restore(dbCtx.MultiStore()) } // calculate a minimum amount of gas to be charged to sender if GasLimit diff --git a/x/evm/statedb/native.go b/x/evm/statedb/native.go new file mode 100644 index 0000000000..8aa1427b4d --- /dev/null +++ b/x/evm/statedb/native.go @@ -0,0 +1,20 @@ +package statedb + +import ( + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/ethereum/go-ethereum/common" +) + +var _ JournalEntry = nativeChange{} + +type nativeChange struct { + snapshot types.CacheMultiStore +} + +func (native nativeChange) Dirtied() *common.Address { + return nil +} + +func (native nativeChange) Revert(s *StateDB) { + s.restoreNativeState(native.snapshot) +} diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 753ec76164..03e71095e6 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -298,6 +298,21 @@ func (s *StateDB) setStateObject(object *stateObject) { s.stateObjects[object.Address()] = object } +func (s *StateDB) restoreNativeState(ms sdk.CacheMultiStore) { + s.ctx = s.ctx.WithMultiStore(ms) +} + +func (s *StateDB) executeNativeAction(action func(ctx sdk.Context) error) error { + snapshot := s.ctx.MultiStore().Clone() + err := action(s.ctx) + if err != nil { + s.restoreNativeState(snapshot) + return err + } + s.journal.append(nativeChange{snapshot: snapshot}) + return nil +} + /* * SETTERS */ @@ -450,19 +465,19 @@ func (s *StateDB) RevertToSnapshot(revid int) { // Commit writes the dirty states to keeper // the StateDB object should be discarded after committed. -func (s *StateDB) Commit() error { +func (s *StateDB) Commit() (sdk.Context, error) { for _, addr := range s.journal.sortedDirties() { obj := s.stateObjects[addr] if obj.suicided { if err := s.keeper.DeleteAccount(s.ctx, obj.Address()); err != nil { - return errorsmod.Wrap(err, "failed to delete account") + return s.ctx, errorsmod.Wrap(err, "failed to delete account") } } else { if obj.code != nil && obj.dirtyCode { s.keeper.SetCode(s.ctx, obj.CodeHash(), obj.code) } if err := s.keeper.SetAccount(s.ctx, obj.Address(), obj.account); err != nil { - return errorsmod.Wrap(err, "failed to set account") + return s.ctx, errorsmod.Wrap(err, "failed to set account") } for _, key := range obj.dirtyStorage.SortedKeys() { value := obj.dirtyStorage[key] @@ -474,5 +489,5 @@ func (s *StateDB) Commit() error { } } } - return nil + return s.ctx, nil } From 5477df94fdb5fd70eeabd7af16a1457d7a6df6a2 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Fri, 17 Feb 2023 10:40:02 +0800 Subject: [PATCH 21/41] vendor clone cache store implementation for statedb --- app/ante/eth.go | 2 +- app/app.go | 2 +- go.mod | 2 +- store/cachekv/README.md | 140 +++++ store/cachekv/bench_helper_test.go | 44 ++ store/cachekv/benchmark_test.go | 133 +++++ store/cachekv/internal/btree.go | 101 ++++ store/cachekv/internal/btree_test.go | 208 +++++++ store/cachekv/internal/memiterator.go | 119 ++++ store/cachekv/internal/mergeiterator.go | 235 ++++++++ store/cachekv/store.go | 179 ++++++ store/cachekv/store_bench_test.go | 149 +++++ store/cachekv/store_test.go | 707 ++++++++++++++++++++++++ store/cachemulti/store.go | 191 +++++++ store/cachemulti/store_test.go | 25 + x/evm/keeper/keeper.go | 9 +- x/evm/keeper/state_transition.go | 7 +- x/evm/statedb/native.go | 2 +- x/evm/statedb/statedb.go | 36 +- 19 files changed, 2271 insertions(+), 20 deletions(-) create mode 100644 store/cachekv/README.md create mode 100644 store/cachekv/bench_helper_test.go create mode 100644 store/cachekv/benchmark_test.go create mode 100644 store/cachekv/internal/btree.go create mode 100644 store/cachekv/internal/btree_test.go create mode 100644 store/cachekv/internal/memiterator.go create mode 100644 store/cachekv/internal/mergeiterator.go create mode 100644 store/cachekv/store.go create mode 100644 store/cachekv/store_bench_test.go create mode 100644 store/cachekv/store_test.go create mode 100644 store/cachemulti/store.go create mode 100644 store/cachemulti/store_test.go diff --git a/app/ante/eth.go b/app/ante/eth.go index 2402167be7..87322f980a 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -302,7 +302,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate BaseFee: baseFee, } - stateDB := statedb.New(ctx, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) + stateDB := statedb.New(ctx, nil, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB) // check that caller has enough balance to cover asset transfer for **topmost** call diff --git a/app/app.go b/app/app.go index 7e5b3e8982..deb3903a7e 100644 --- a/app/app.go +++ b/app/app.go @@ -423,7 +423,7 @@ func NewEthermintApp( app.EvmKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.FeeMarketKeeper, - nil, geth.NewEVM, tracer, evmSs, + nil, geth.NewEVM, tracer, evmSs, keys, ) // Create IBC Keeper diff --git a/go.mod b/go.mod index 7ae23473bf..7ad64f306e 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/stretchr/testify v1.8.1 github.com/tendermint/tendermint v0.34.24 github.com/tendermint/tm-db v0.6.7 + github.com/tidwall/btree v1.5.0 github.com/tyler-smith/go-bip39 v1.1.0 golang.org/x/net v0.6.0 golang.org/x/text v0.7.0 @@ -163,7 +164,6 @@ require ( github.com/subosito/gotenv v1.4.2 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tendermint/go-amino v0.16.0 // indirect - github.com/tidwall/btree v1.5.0 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect github.com/ulikunitz/xz v0.5.8 // indirect diff --git a/store/cachekv/README.md b/store/cachekv/README.md new file mode 100644 index 0000000000..66f0916dea --- /dev/null +++ b/store/cachekv/README.md @@ -0,0 +1,140 @@ +# CacheKVStore specification + +A `CacheKVStore` is cache wrapper for a `KVStore`. It extends the operations of the `KVStore` to work with a write-back cache, allowing for reduced I/O operations and more efficient disposing of changes (e.g. after processing a failed transaction). + +The core goals the CacheKVStore seeks to solve are: + +* Buffer all writes to the parent store, so they can be dropped if they need to be reverted +* Allow iteration over contiguous spans of keys +* Act as a cache, improving access time for reads that have already been done (by replacing tree access with hashtable access, avoiding disk I/O) + * Note: We actually fail to achieve this for iteration right now + * Note: Need to consider this getting too large and dropping some cached reads +* Make subsequent reads account for prior buffered writes +* Write all buffered changes to the parent store + +We should revisit these goals with time (for instance it's unclear that all disk writes need to be buffered to the end of the block), but this is the current status. + +## Types and Structs + +```go +type Store struct { + mtx sync.Mutex + cache map[string]*cValue + deleted map[string]struct{} + unsortedCache map[string]struct{} + sortedCache *dbm.MemDB // always ascending sorted + parent types.KVStore +} +``` + +The Store struct wraps the underlying `KVStore` (`parent`) with additional data structures for implementing the cache. Mutex is used as IAVL trees (the `KVStore` in application) are not safe for concurrent use. + +### `cache` + +The main mapping of key-value pairs stored in cache. This map contains both keys that are cached from read operations as well as ‘dirty’ keys which map to a value that is potentially different than what is in the underlying `KVStore`. + +Values that are mapped to in `cache` are wrapped in a `cValue` struct, which contains the value and a boolean flag (`dirty`) representing whether the value has been written since the last write-back to `parent`. + +```go +type cValue struct { + value []byte + dirty bool +} +``` + +### `deleted` + +Key-value pairs that are to be deleted from `parent` are stored in the `deleted` map. Keys are mapped to an empty struct to implement a set. + +### `unsortedCache` + +Similar to `deleted`, this is a set of keys that are dirty and will need to be updated in the parent `KVStore` upon a write. Keys are mapped to an empty struct to implement a set. + +### `sortedCache` + +A database that will be populated by the keys in `unsortedCache` during iteration over the cache. The keys are always held in sorted order. + +## CRUD Operations and Writing + +The `Set`, `Get`, and `Delete` functions all call `setCacheValue()`, which is the only entry point to mutating `cache` (besides `Write()`, which clears it). + +`setCacheValue()` inserts a key-value pair into `cache`. Two boolean parameters, `deleted` and `dirty`, are passed in to flag whether the inserted key should also be inserted into the `deleted` and `dirty` sets. Keys will be removed from the `deleted` set if they are written to after being deleted. + +### `Get` + +`Get` first attempts to return the value from `cache`. If the key does not exist in `cache`, `parent.Get()` is called instead. This value from the parent is passed into `setCacheValue()` with `deleted=false` and `dirty=false`. + +### `Has` + +`Has` returns true if `Get` returns a non-nil value. As a result of calling `Get`, it may mutate the cache by caching the read. + +### `Set` + +New values are written by setting or updating the value of a key in `cache`. `Set` does not write to `parent`. + +Calls `setCacheValue()` with `deleted=false` and `dirty=true`. + +### `Delete` + +A value being deleted from the `KVStore` is represented with a `nil` value in `cache`, and an insertion of the key into the `deleted` set. `Delete` does not write to `parent`. + +Calls `setCacheValue()` with `deleted=true` and `dirty=true`. + +### `Write` + +Key-value pairs in the cache are written to `parent` in ascending order of their keys. + +A slice of all dirty keys in `cache` is made, then sorted in increasing order. These keys are iterated over to update `parent`. + +If a key is marked for deletion (checked with `isDeleted()`), then `parent.Delete()` is called. Otherwise, `parent.Set()` is called to update the underlying `KVStore` with the value in cache. + +## Iteration + +Efficient iteration over keys in `KVStore` is important for generating Merkle range proofs. Iteration over `CacheKVStore` requires producing all key-value pairs from the underlying `KVStore` while taking into account updated values from the cache. + +In the current implementation, there is no guarantee that all values in `parent` have been cached. As a result, iteration is achieved by interleaved iteration through both `parent` and the cache (failing to actually benefit from caching). + +[cacheMergeIterator](https://github.com/cosmos/cosmos-sdk/blob/d8391cb6796d770b02448bee70b865d824e43449/store/cachekv/mergeiterator.go) implements functions to provide a single iterator with an input of iterators over `parent` and the cache. This iterator iterates over keys from both iterators in a shared lexicographic order, and overrides the value provided by the parent iterator if the same key is dirty or deleted in the cache. + +### Implementation Overview + +Iterators over `parent` and the cache are generated and passed into `cacheMergeIterator`, which returns a single, interleaved iterator. Implementation of the `parent` iterator is up to the underlying `KVStore`. The remainder of this section covers the generation of the cache iterator. + +Recall that `unsortedCache` is an unordered set of dirty cache keys. Our goal is to construct an ordered iterator over cache keys that fall within the `start` and `end` bounds requested. + +Generating the cache iterator can be decomposed into four parts: + +1. Finding all keys that exist in the range we are iterating over +2. Sorting this list of keys +3. Inserting these keys into `sortedCache` and removing them from `unsortedCache` +4. Returning an iterator over `sortedCache` with the desired range + +Currently, the implementation for the first two parts is split into two cases, depending on the size of the unsorted cache. The two cases are as follows. + +If the size of `unsortedCache` is less than `minSortSize` (currently 1024), a linear time approach is taken to search over keys. + +```go +n := len(store.unsortedCache) +unsorted := make([]*kv.Pair, 0) + +if n < minSortSize { + for key := range store.unsortedCache { + if dbm.IsKeyInDomain(conv.UnsafeStrToBytes(key), start, end) { + cacheValue := store.cache[key] + unsorted = append(unsorted, &kv.Pair{Key: []byte(key), Value: cacheValue.value}) + } + } + store.clearUnsortedCacheSubset(unsorted, stateUnsorted) + return +} +``` + +Here, we iterate through all the keys in `unsortedCache` (i.e., the dirty cache keys), collecting those within the requested range in an unsorted slice called `unsorted`. + +At this point, part 3. is achieved in `clearUnsortedCacheSubset()`. This function iterates through `unsorted`, removing each key from `unsortedCache`. Afterwards, `unsorted` is sorted. Lastly, it iterates through the now sorted slice, inserting key-value pairs into `sortedCache`. Any key marked for deletion is mapped to an arbitrary value (`[]byte{}`). + +In the case that the size of `unsortedCache` is larger than `minSortSize`, a linear time approach to finding keys within the desired range is too slow to use. Instead, a slice of all keys in `unsortedCache` is sorted, and binary search is used to find the beginning and ending indices of the desired range. This produces an already-sorted slice that is passed into the same `clearUnsortedCacheSubset()` function. An iota identifier (`sortedState`) is used to skip the sorting step in the function. + +Finally, part 4. is achieved with `memIterator`, which implements an iterator over the items in `sortedCache`. + +As of [PR #12885](https://github.com/cosmos/cosmos-sdk/pull/12885), an optimization to the binary search case mitigates the overhead of sorting the entirety of the key set in `unsortedCache`. To avoid wasting the compute spent sorting, we should ensure that a reasonable amount of values are removed from `unsortedCache`. If the length of the range for iteration is less than `minSortedCache`, we widen the range of values for removal from `unsortedCache` to be up to `minSortedCache` in length. This amortizes the cost of processing elements across multiple calls. \ No newline at end of file diff --git a/store/cachekv/bench_helper_test.go b/store/cachekv/bench_helper_test.go new file mode 100644 index 0000000000..fe5be27fab --- /dev/null +++ b/store/cachekv/bench_helper_test.go @@ -0,0 +1,44 @@ +package cachekv_test + +import "crypto/rand" + +func randSlice(sliceSize int) []byte { + bz := make([]byte, sliceSize) + _, _ = rand.Read(bz) + return bz +} + +func incrementByteSlice(bz []byte) { + for index := len(bz) - 1; index >= 0; index-- { + if bz[index] < 255 { + bz[index]++ + break + } else { + bz[index] = 0 + } + } +} + +// Generate many keys starting at startKey, and are in sequential order +func generateSequentialKeys(startKey []byte, numKeys int) [][]byte { + toReturn := make([][]byte, 0, numKeys) + cur := make([]byte, len(startKey)) + copy(cur, startKey) + for i := 0; i < numKeys; i++ { + newKey := make([]byte, len(startKey)) + copy(newKey, cur) + toReturn = append(toReturn, newKey) + incrementByteSlice(cur) + } + return toReturn +} + +// Generate many random, unsorted keys +func generateRandomKeys(keySize int, numKeys int) [][]byte { + toReturn := make([][]byte, 0, numKeys) + for i := 0; i < numKeys; i++ { + newKey := randSlice(keySize) + toReturn = append(toReturn, newKey) + } + return toReturn +} diff --git a/store/cachekv/benchmark_test.go b/store/cachekv/benchmark_test.go new file mode 100644 index 0000000000..cefb467a91 --- /dev/null +++ b/store/cachekv/benchmark_test.go @@ -0,0 +1,133 @@ +package cachekv_test + +import ( + fmt "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/evmos/ethermint/store/cachekv" + "github.com/stretchr/testify/require" + dbm "github.com/tendermint/tm-db" +) + +func DoBenchmarkDeepCacheStack(b *testing.B, depth int) { + db := dbm.NewMemDB() + initialStore := cachekv.NewStore(dbadapter.Store{DB: db}) + + nItems := 20 + for i := 0; i < nItems; i++ { + initialStore.Set([]byte(fmt.Sprintf("hello%03d", i)), []byte{0}) + } + + var stack CacheStack + stack.Reset(initialStore) + + for i := 0; i < depth; i++ { + stack.Snapshot() + + store := stack.CurrentStore() + store.Set([]byte(fmt.Sprintf("hello%03d", i)), []byte{byte(i)}) + } + + store := stack.CurrentStore() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + it := store.Iterator(nil, nil) + items := make([][]byte, 0, nItems) + for ; it.Valid(); it.Next() { + items = append(items, it.Key()) + it.Value() + } + it.Close() + require.Equal(b, nItems, len(items)) + } +} + +func BenchmarkDeepCacheStack1(b *testing.B) { + DoBenchmarkDeepCacheStack(b, 1) +} + +func BenchmarkDeepCacheStack3(b *testing.B) { + DoBenchmarkDeepCacheStack(b, 3) +} + +func BenchmarkDeepCacheStack10(b *testing.B) { + DoBenchmarkDeepCacheStack(b, 10) +} + +func BenchmarkDeepCacheStack13(b *testing.B) { + DoBenchmarkDeepCacheStack(b, 13) +} + +// CacheStack manages a stack of nested cache store to +// support the evm `StateDB`'s `Snapshot` and `RevertToSnapshot` methods. +type CacheStack struct { + initialStore *cachekv.Store + // Context of the initial state before transaction execution. + // It's the context used by `StateDB.CommitedState`. + cacheStores []*cachekv.Store +} + +// CurrentContext returns the top context of cached stack, +// if the stack is empty, returns the initial context. +func (cs *CacheStack) CurrentStore() *cachekv.Store { + l := len(cs.cacheStores) + if l == 0 { + return cs.initialStore + } + return cs.cacheStores[l-1] +} + +// Reset sets the initial context and clear the cache context stack. +func (cs *CacheStack) Reset(initialStore *cachekv.Store) { + cs.initialStore = initialStore + cs.cacheStores = nil +} + +// IsEmpty returns true if the cache context stack is empty. +func (cs *CacheStack) IsEmpty() bool { + return len(cs.cacheStores) == 0 +} + +// Commit commits all the cached contexts from top to bottom in order and clears the stack by setting an empty slice of cache contexts. +func (cs *CacheStack) Commit() { + // commit in order from top to bottom + for i := len(cs.cacheStores) - 1; i >= 0; i-- { + cs.cacheStores[i].Write() + } + cs.cacheStores = nil +} + +// CommitToRevision commit the cache after the target revision, +// to improve efficiency of db operations. +func (cs *CacheStack) CommitToRevision(target int) error { + if target < 0 || target >= len(cs.cacheStores) { + return fmt.Errorf("snapshot index %d out of bound [%d..%d)", target, 0, len(cs.cacheStores)) + } + + // commit in order from top to bottom + for i := len(cs.cacheStores) - 1; i > target; i-- { + cs.cacheStores[i].Write() + } + cs.cacheStores = cs.cacheStores[0 : target+1] + + return nil +} + +// Snapshot pushes a new cached context to the stack, +// and returns the index of it. +func (cs *CacheStack) Snapshot() int { + cs.cacheStores = append(cs.cacheStores, cs.CurrentStore().Clone()) + return len(cs.cacheStores) - 1 +} + +// RevertToSnapshot pops all the cached contexts after the target index (inclusive). +// the target should be snapshot index returned by `Snapshot`. +// This function panics if the index is out of bounds. +func (cs *CacheStack) RevertToSnapshot(target int) { + if target < 0 || target >= len(cs.cacheStores) { + panic(fmt.Errorf("snapshot index %d out of bound [%d..%d)", target, 0, len(cs.cacheStores))) + } + cs.cacheStores = cs.cacheStores[:target] +} diff --git a/store/cachekv/internal/btree.go b/store/cachekv/internal/btree.go new file mode 100644 index 0000000000..d825a365de --- /dev/null +++ b/store/cachekv/internal/btree.go @@ -0,0 +1,101 @@ +package internal + +import ( + "bytes" + "errors" + + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/tidwall/btree" +) + +const ( + // The approximate number of items and children per B-tree node. Tuned with benchmarks. + // copied from memdb. + bTreeDegree = 32 +) + +var errKeyEmpty = errors.New("key cannot be empty") + +// BTree implements the sorted cache for cachekv store, +// we don't use MemDB here because cachekv is used extensively in sdk core path, +// we need it to be as fast as possible, while `MemDB` is mainly used as a mocking db in unit tests. +// +// We choose tidwall/btree over google/btree here because it provides API to implement step iterator directly. +type BTree struct { + tree *btree.BTreeG[item] +} + +// NewBTree creates a wrapper around `btree.BTreeG`. +func NewBTree() BTree { + return BTree{ + tree: btree.NewBTreeGOptions(byKeys, btree.Options{ + Degree: bTreeDegree, + NoLocks: false, + }), + } +} + +func (bt BTree) Set(key, value []byte, dirty bool) { + bt.tree.Set(item{key: key, value: value, dirty: dirty}) +} + +func (bt BTree) Get(key []byte) ([]byte, bool) { + i, found := bt.tree.Get(newItem(key, nil)) + if !found { + return nil, false + } + return i.value, true +} + +func (bt BTree) Delete(key []byte) { + bt.tree.Delete(newItem(key, nil)) +} + +func (bt BTree) Iterator(start, end []byte) (types.Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, errKeyEmpty + } + return newMemIterator(start, end, bt, true), nil +} + +func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, errKeyEmpty + } + return newMemIterator(start, end, bt, false), nil +} + +// ScanDirtyItems iterate over the dirty entries. +func (bt BTree) ScanDirtyItems(fn func(key, value []byte)) { + bt.tree.Scan(func(item item) bool { + if item.dirty { + fn(item.key, item.value) + } + return true + }) +} + +// Copy the tree. This is a copy-on-write operation and is very fast because +// it only performs a shadowed copy. +func (bt BTree) Copy() BTree { + return BTree{ + tree: bt.tree.Copy(), + } +} + +// item is a btree item with byte slices as keys and values +type item struct { + key []byte + value []byte + dirty bool +} + +// byKeys compares the items by key +func byKeys(a, b item) bool { + return bytes.Compare(a.key, b.key) == -1 +} + +// newItem creates a new pair item. +func newItem(key, value []byte) item { + return item{key: key, value: value} +} diff --git a/store/cachekv/internal/btree_test.go b/store/cachekv/internal/btree_test.go new file mode 100644 index 0000000000..bd19449ba4 --- /dev/null +++ b/store/cachekv/internal/btree_test.go @@ -0,0 +1,208 @@ +package internal + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestGetSetDelete(t *testing.T) { + db := NewBTree() + + // A nonexistent key should return nil. + value, found := db.Get([]byte("a")) + require.Nil(t, value) + require.False(t, found) + + // Set and get a value. + db.Set([]byte("a"), []byte{0x01}, true) + db.Set([]byte("b"), []byte{0x02}, true) + value, found = db.Get([]byte("a")) + require.Equal(t, []byte{0x01}, value) + require.True(t, found) + + value, found = db.Get([]byte("b")) + require.Equal(t, []byte{0x02}, value) + require.True(t, found) + + // Deleting a non-existent value is fine. + db.Delete([]byte("x")) + + // Delete a value. + db.Delete([]byte("a")) + + value, found = db.Get([]byte("a")) + require.Nil(t, value) + require.False(t, found) + + db.Delete([]byte("b")) + + value, found = db.Get([]byte("b")) + require.Nil(t, value) + require.False(t, found) +} + +func TestDBIterator(t *testing.T) { + db := NewBTree() + + for i := 0; i < 10; i++ { + if i != 6 { // but skip 6. + db.Set(int642Bytes(int64(i)), []byte{}, true) + } + } + + // Blank iterator keys should error + _, err := db.ReverseIterator([]byte{}, nil) + require.Equal(t, errKeyEmpty, err) + _, err = db.ReverseIterator(nil, []byte{}) + require.Equal(t, errKeyEmpty, err) + + itr, err := db.Iterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator") + + ritr, err := db.ReverseIterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator") + + itr, err = db.Iterator(nil, int642Bytes(0)) + require.NoError(t, err) + verifyIterator(t, itr, []int64(nil), "forward iterator to 0") + + ritr, err = db.ReverseIterator(int642Bytes(10), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64(nil), "reverse iterator from 10 (ex)") + + itr, err = db.Iterator(int642Bytes(0), nil) + require.NoError(t, err) + verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 0") + + itr, err = db.Iterator(int642Bytes(1), nil) + require.NoError(t, err) + verifyIterator(t, itr, []int64{1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 1") + + ritr, err = db.ReverseIterator(nil, int642Bytes(10)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 10 (ex)") + + ritr, err = db.ReverseIterator(nil, int642Bytes(9)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 9 (ex)") + + ritr, err = db.ReverseIterator(nil, int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 8 (ex)") + + itr, err = db.Iterator(int642Bytes(5), int642Bytes(6)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 6") + + itr, err = db.Iterator(int642Bytes(5), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 7") + + itr, err = db.Iterator(int642Bytes(5), int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{5, 7}, "forward iterator from 5 to 8") + + itr, err = db.Iterator(int642Bytes(6), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, itr, []int64(nil), "forward iterator from 6 to 7") + + itr, err = db.Iterator(int642Bytes(6), int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{7}, "forward iterator from 6 to 8") + + itr, err = db.Iterator(int642Bytes(7), int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{7}, "forward iterator from 7 to 8") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(5)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{4}, "reverse iterator from 5 (ex) to 4") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(6)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{5, 4}, "reverse iterator from 6 (ex) to 4") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{5, 4}, "reverse iterator from 7 (ex) to 4") + + ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(6)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{5}, "reverse iterator from 6 (ex) to 5") + + ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{5}, "reverse iterator from 7 (ex) to 5") + + ritr, err = db.ReverseIterator(int642Bytes(6), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64(nil), "reverse iterator from 7 (ex) to 6") + + ritr, err = db.ReverseIterator(int642Bytes(10), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64(nil), "reverse iterator to 10") + + ritr, err = db.ReverseIterator(int642Bytes(6), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{9, 8, 7}, "reverse iterator to 6") + + ritr, err = db.ReverseIterator(int642Bytes(5), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{9, 8, 7, 5}, "reverse iterator to 5") + + ritr, err = db.ReverseIterator(int642Bytes(8), int642Bytes(9)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{8}, "reverse iterator from 9 (ex) to 8") + + ritr, err = db.ReverseIterator(int642Bytes(2), int642Bytes(4)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{3, 2}, "reverse iterator from 4 (ex) to 2") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(2)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64(nil), "reverse iterator from 2 (ex) to 4") + + // Ensure that the iterators don't panic with an empty database. + db2 := NewBTree() + + itr, err = db2.Iterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, itr, nil, "forward iterator with empty db") + + ritr, err = db2.ReverseIterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, ritr, nil, "reverse iterator with empty db") +} + +func verifyIterator(t *testing.T, itr types.Iterator, expected []int64, msg string) { + i := 0 + for itr.Valid() { + key := itr.Key() + require.Equal(t, expected[i], bytes2Int64(key), "iterator: %d mismatches", i) + itr.Next() + i++ + } + require.Equal(t, i, len(expected), "expected to have fully iterated over all the elements in iter") + require.NoError(t, itr.Close()) +} + +func int642Bytes(i int64) []byte { + return sdk.Uint64ToBigEndian(uint64(i)) +} + +func bytes2Int64(buf []byte) int64 { + return int64(sdk.BigEndianToUint64(buf)) +} diff --git a/store/cachekv/internal/memiterator.go b/store/cachekv/internal/memiterator.go new file mode 100644 index 0000000000..b7960945ee --- /dev/null +++ b/store/cachekv/internal/memiterator.go @@ -0,0 +1,119 @@ +package internal + +import ( + "bytes" + "errors" + + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/tidwall/btree" +) + +var _ types.Iterator = (*memIterator)(nil) + +// memIterator iterates over iterKVCache items. +// if value is nil, means it was deleted. +// Implements Iterator. +type memIterator struct { + iter btree.GenericIter[item] + + start []byte + end []byte + ascending bool + valid bool +} + +func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator { + iter := items.tree.Iter() + var valid bool + if ascending { + if start != nil { + valid = iter.Seek(newItem(start, nil)) + } else { + valid = iter.First() + } + } else { + if end != nil { + valid = iter.Seek(newItem(end, nil)) + if !valid { + valid = iter.Last() + } else { + // end is exclusive + valid = iter.Prev() + } + } else { + valid = iter.Last() + } + } + + mi := &memIterator{ + iter: iter, + start: start, + end: end, + ascending: ascending, + valid: valid, + } + + if mi.valid { + mi.valid = mi.keyInRange(mi.Key()) + } + + return mi +} + +func (mi *memIterator) Domain() (start []byte, end []byte) { + return mi.start, mi.end +} + +func (mi *memIterator) Close() error { + mi.iter.Release() + return nil +} + +func (mi *memIterator) Error() error { + if !mi.Valid() { + return errors.New("invalid memIterator") + } + return nil +} + +func (mi *memIterator) Valid() bool { + return mi.valid +} + +func (mi *memIterator) Next() { + mi.assertValid() + + if mi.ascending { + mi.valid = mi.iter.Next() + } else { + mi.valid = mi.iter.Prev() + } + + if mi.valid { + mi.valid = mi.keyInRange(mi.Key()) + } +} + +func (mi *memIterator) keyInRange(key []byte) bool { + if mi.ascending && mi.end != nil && bytes.Compare(key, mi.end) >= 0 { + return false + } + if !mi.ascending && mi.start != nil && bytes.Compare(key, mi.start) < 0 { + return false + } + return true +} + +func (mi *memIterator) Key() []byte { + return mi.iter.Item().key +} + +func (mi *memIterator) Value() []byte { + return mi.iter.Item().value +} + +func (mi *memIterator) assertValid() { + if err := mi.Error(); err != nil { + panic(err) + } +} diff --git a/store/cachekv/internal/mergeiterator.go b/store/cachekv/internal/mergeiterator.go new file mode 100644 index 0000000000..293bc968e7 --- /dev/null +++ b/store/cachekv/internal/mergeiterator.go @@ -0,0 +1,235 @@ +package internal + +import ( + "bytes" + "errors" + + "github.com/cosmos/cosmos-sdk/store/types" +) + +// cacheMergeIterator merges a parent Iterator and a cache Iterator. +// The cache iterator may return nil keys to signal that an item +// had been deleted (but not deleted in the parent). +// If the cache iterator has the same key as the parent, the +// cache shadows (overrides) the parent. +// +// TODO: Optimize by memoizing. +type cacheMergeIterator struct { + parent types.Iterator + cache types.Iterator + ascending bool + + valid bool +} + +var _ types.Iterator = (*cacheMergeIterator)(nil) + +func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) *cacheMergeIterator { //nolint:revive + iter := &cacheMergeIterator{ + parent: parent, + cache: cache, + ascending: ascending, + } + + iter.valid = iter.skipUntilExistsOrInvalid() + return iter +} + +// Domain implements Iterator. +// Returns parent domain because cache and parent domains are the same. +func (iter *cacheMergeIterator) Domain() (start, end []byte) { + return iter.parent.Domain() +} + +// Valid implements Iterator. +func (iter *cacheMergeIterator) Valid() bool { + return iter.valid +} + +// Next implements Iterator +func (iter *cacheMergeIterator) Next() { + iter.assertValid() + + switch { + case !iter.parent.Valid(): + // If parent is invalid, get the next cache item. + iter.cache.Next() + case !iter.cache.Valid(): + // If cache is invalid, get the next parent item. + iter.parent.Next() + default: + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + switch iter.compare(keyP, keyC) { + case -1: // parent < cache + iter.parent.Next() + case 0: // parent == cache + iter.parent.Next() + iter.cache.Next() + case 1: // parent > cache + iter.cache.Next() + } + } + iter.valid = iter.skipUntilExistsOrInvalid() +} + +// Key implements Iterator +func (iter *cacheMergeIterator) Key() []byte { + iter.assertValid() + + // If parent is invalid, get the cache key. + if !iter.parent.Valid() { + return iter.cache.Key() + } + + // If cache is invalid, get the parent key. + if !iter.cache.Valid() { + return iter.parent.Key() + } + + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + + cmp := iter.compare(keyP, keyC) + switch cmp { + case -1: // parent < cache + return keyP + case 0: // parent == cache + return keyP + case 1: // parent > cache + return keyC + default: + panic("invalid compare result") + } +} + +// Value implements Iterator +func (iter *cacheMergeIterator) Value() []byte { + iter.assertValid() + + // If parent is invalid, get the cache value. + if !iter.parent.Valid() { + return iter.cache.Value() + } + + // If cache is invalid, get the parent value. + if !iter.cache.Valid() { + return iter.parent.Value() + } + + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + + cmp := iter.compare(keyP, keyC) + switch cmp { + case -1: // parent < cache + return iter.parent.Value() + case 0: // parent == cache + return iter.cache.Value() + case 1: // parent > cache + return iter.cache.Value() + default: + panic("invalid comparison result") + } +} + +// Close implements Iterator +func (iter *cacheMergeIterator) Close() error { + err1 := iter.cache.Close() + if err := iter.parent.Close(); err != nil { + return err + } + + return err1 +} + +// Error returns an error if the cacheMergeIterator is invalid defined by the +// Valid method. +func (iter *cacheMergeIterator) Error() error { + if !iter.Valid() { + return errors.New("invalid cacheMergeIterator") + } + + return nil +} + +// If not valid, panics. +// NOTE: May have side-effect of iterating over cache. +func (iter *cacheMergeIterator) assertValid() { + if err := iter.Error(); err != nil { + panic(err) + } +} + +// Like bytes.Compare but opposite if not ascending. +func (iter *cacheMergeIterator) compare(a, b []byte) int { + if iter.ascending { + return bytes.Compare(a, b) + } + + return bytes.Compare(a, b) * -1 +} + +// Skip all delete-items from the cache w/ `key < until`. After this function, +// current cache item is a non-delete-item, or `until <= key`. +// If the current cache item is not a delete item, does nothing. +// If `until` is nil, there is no limit, and cache may end up invalid. +// CONTRACT: cache is valid. +func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) { + for iter.cache.Valid() && + iter.cache.Value() == nil && + (until == nil || iter.compare(iter.cache.Key(), until) < 0) { + iter.cache.Next() + } +} + +// Fast forwards cache (or parent+cache in case of deleted items) until current +// item exists, or until iterator becomes invalid. +// Returns whether the iterator is valid. +func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool { + for { + // If parent is invalid, fast-forward cache. + if !iter.parent.Valid() { + iter.skipCacheDeletes(nil) + return iter.cache.Valid() + } + // Parent is valid. + + if !iter.cache.Valid() { + return true + } + // Parent is valid, cache is valid. + + // Compare parent and cache. + keyP := iter.parent.Key() + keyC := iter.cache.Key() + + switch iter.compare(keyP, keyC) { + case -1: // parent < cache. + return true + + case 0: // parent == cache. + // Skip over if cache item is a delete. + valueC := iter.cache.Value() + if valueC == nil { + iter.parent.Next() + iter.cache.Next() + + continue + } + // Cache is not a delete. + + return true // cache exists. + case 1: // cache < parent + // Skip over if cache item is a delete. + valueC := iter.cache.Value() + if valueC == nil { + iter.skipCacheDeletes(keyP) + continue + } + // Cache is not a delete. + + return true // cache exists. + } + } +} diff --git a/store/cachekv/store.go b/store/cachekv/store.go new file mode 100644 index 0000000000..20c6bc0b62 --- /dev/null +++ b/store/cachekv/store.go @@ -0,0 +1,179 @@ +package cachekv + +import ( + "io" + "sync" + + "github.com/cosmos/cosmos-sdk/store/tracekv" + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/evmos/ethermint/store/cachekv/internal" +) + +// Store wraps an in-memory cache around an underlying types.KVStore. +type Store struct { + mtx sync.Mutex + cache internal.BTree // always ascending sorted + parent types.KVStore +} + +var _ types.CacheKVStore = (*Store)(nil) + +// NewStore creates a new Store object +func NewStore(parent types.KVStore) *Store { + return &Store{ + cache: internal.NewBTree(), + parent: parent, + } +} + +// GetStoreType implements Store. +func (store *Store) GetStoreType() types.StoreType { + return store.parent.GetStoreType() +} + +// Clone creates a snapshot of the cache store. +// This is a copy-on-write operation and is very fast because +// it only performs a shadowed copy. +func (store *Store) Clone() *Store { + store.mtx.Lock() + defer store.mtx.Unlock() + + return &Store{ + cache: store.cache.Copy(), + parent: store.parent, + } +} + +// swapCache swap out the internal cache store and leave the current store in a unusable state. +func (store *Store) swapCache() internal.BTree { + store.mtx.Lock() + defer store.mtx.Unlock() + + cache := store.cache + store.cache = internal.BTree{} + return cache +} + +// Restore restores the store cache to a given snapshot. +func (store *Store) Restore(s types.CacheKVStore) { + cache := s.(*Store).swapCache() + + store.mtx.Lock() + defer store.mtx.Unlock() + + store.cache = cache +} + +// Get implements types.KVStore. +func (store *Store) Get(key []byte) (value []byte) { + store.mtx.Lock() + defer store.mtx.Unlock() + + types.AssertValidKey(key) + + if value, found := store.cache.Get(key); found { + return value + } + value = store.parent.Get(key) + store.setCacheValue(key, value, false) + return value +} + +// Set implements types.KVStore. +func (store *Store) Set(key []byte, value []byte) { + store.mtx.Lock() + defer store.mtx.Unlock() + + types.AssertValidKey(key) + types.AssertValidValue(value) + + store.setCacheValue(key, value, true) +} + +// Has implements types.KVStore. +func (store *Store) Has(key []byte) bool { + value := store.Get(key) + return value != nil +} + +// Delete implements types.KVStore. +func (store *Store) Delete(key []byte) { + store.mtx.Lock() + defer store.mtx.Unlock() + + types.AssertValidKey(key) + store.setCacheValue(key, nil, true) +} + +// Implements Cachetypes.KVStore. +func (store *Store) Write() { + store.mtx.Lock() + defer store.mtx.Unlock() + + store.cache.ScanDirtyItems(func(key, value []byte) { + if value == nil { + store.parent.Delete(key) + } else { + store.parent.Set(key, value) + } + }) + + store.cache = internal.NewBTree() +} + +// CacheWrap implements CacheWrapper. +func (store *Store) CacheWrap() types.CacheWrap { + return NewStore(store) +} + +// CacheWrapWithTrace implements the CacheWrapper interface. +func (store *Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap { + return NewStore(tracekv.NewStore(store, w, tc)) +} + +//---------------------------------------- +// Iteration + +// Iterator implements types.KVStore. +func (store *Store) Iterator(start, end []byte) types.Iterator { + return store.iterator(start, end, true) +} + +// ReverseIterator implements types.KVStore. +func (store *Store) ReverseIterator(start, end []byte) types.Iterator { + return store.iterator(start, end, false) +} + +func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { + store.mtx.Lock() + defer store.mtx.Unlock() + + isoSortedCache := store.cache.Copy() + + var ( + err error + parent, cache types.Iterator + ) + + if ascending { + parent = store.parent.Iterator(start, end) + cache, err = isoSortedCache.Iterator(start, end) + } else { + parent = store.parent.ReverseIterator(start, end) + cache, err = isoSortedCache.ReverseIterator(start, end) + } + if err != nil { + panic(err) + } + + return internal.NewCacheMergeIterator(parent, cache, ascending) +} + +//---------------------------------------- +// etc + +// Only entrypoint to mutate store.cache. +// A `nil` value means a deletion. +func (store *Store) setCacheValue(key, value []byte, dirty bool) { + store.cache.Set(key, value, dirty) +} diff --git a/store/cachekv/store_bench_test.go b/store/cachekv/store_bench_test.go new file mode 100644 index 0000000000..7f4b9f57cf --- /dev/null +++ b/store/cachekv/store_bench_test.go @@ -0,0 +1,149 @@ +package cachekv_test + +import ( + "testing" + + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/evmos/ethermint/store/cachekv" +) + +var sink interface{} + +const defaultValueSizeBz = 1 << 12 + +// This benchmark measures the time of iterator.Next() when the parent store is blank +func benchmarkBlankParentIteratorNext(b *testing.B, keysize int) { + mem := dbadapter.Store{DB: dbm.NewMemDB()} + kvstore := cachekv.NewStore(mem) + // Use a singleton for value, to not waste time computing it + value := randSlice(defaultValueSizeBz) + // Use simple values for keys, pick a random start, + // and take next b.N keys sequentially after.] + startKey := randSlice(32) + + // Add 1 to avoid issues when b.N = 1 + keys := generateSequentialKeys(startKey, b.N+1) + for _, k := range keys { + kvstore.Set(k, value) + } + + b.ReportAllocs() + b.ResetTimer() + + iter := kvstore.Iterator(keys[0], keys[b.N]) + defer iter.Close() + + for ; iter.Valid(); iter.Next() { + _ = iter.Key() + // deadcode elimination stub + sink = iter + } +} + +// Benchmark setting New keys to a store, where the new keys are in sequence. +func benchmarkBlankParentAppend(b *testing.B, keysize int) { + mem := dbadapter.Store{DB: dbm.NewMemDB()} + kvstore := cachekv.NewStore(mem) + + // Use a singleton for value, to not waste time computing it + value := randSlice(32) + // Use simple values for keys, pick a random start, + // and take next b.N keys sequentially after. + startKey := randSlice(32) + + keys := generateSequentialKeys(startKey, b.N) + + b.ReportAllocs() + b.ResetTimer() + + for _, k := range keys { + kvstore.Set(k, value) + } +} + +// Benchmark setting New keys to a store, where the new keys are random. +// the speed of this function does not depend on the values in the parent store +func benchmarkRandomSet(b *testing.B, keysize int) { + mem := dbadapter.Store{DB: dbm.NewMemDB()} + kvstore := cachekv.NewStore(mem) + + // Use a singleton for value, to not waste time computing it + value := randSlice(defaultValueSizeBz) + // Add 1 to avoid issues when b.N = 1 + keys := generateRandomKeys(keysize, b.N+1) + + b.ReportAllocs() + b.ResetTimer() + + for _, k := range keys { + kvstore.Set(k, value) + } + + iter := kvstore.Iterator(keys[0], keys[b.N]) + defer iter.Close() + + for ; iter.Valid(); iter.Next() { + _ = iter.Key() + // deadcode elimination stub + sink = iter + } +} + +// Benchmark creating an iterator on a parent with D entries, +// that are all deleted in the cacheKV store. +// We essentially are benchmarking the cacheKV iterator creation & iteration times +// with the number of entries deleted in the parent. +func benchmarkIteratorOnParentWithManyDeletes(b *testing.B, numDeletes int) { + mem := dbadapter.Store{DB: dbm.NewMemDB()} + + // Use a singleton for value, to not waste time computing it + value := randSlice(32) + // Use simple values for keys, pick a random start, + // and take next D keys sequentially after. + startKey := randSlice(32) + // Add 1 to avoid issues when numDeletes = 1 + keys := generateSequentialKeys(startKey, numDeletes+1) + // setup parent db with D keys. + for _, k := range keys { + mem.Set(k, value) + } + kvstore := cachekv.NewStore(mem) + // Delete all keys from the cache KV store. + // The keys[1:] is to keep at least one entry in parent, due to a bug in the SDK iterator design. + // Essentially the iterator will never be valid, in that it should never run. + // However, this is incompatible with the for loop structure the SDK uses, hence + // causes a panic. Thus we do keys[1:]. + for _, k := range keys[1:] { + kvstore.Delete(k) + } + + b.ReportAllocs() + b.ResetTimer() + + iter := kvstore.Iterator(keys[0], keys[numDeletes]) + defer iter.Close() + + for ; iter.Valid(); iter.Next() { + _ = iter.Key() + // deadcode elimination stub + sink = iter + } +} + +func BenchmarkBlankParentIteratorNextKeySize32(b *testing.B) { + benchmarkBlankParentIteratorNext(b, 32) +} + +func BenchmarkBlankParentAppendKeySize32(b *testing.B) { + benchmarkBlankParentAppend(b, 32) +} + +func BenchmarkSetKeySize32(b *testing.B) { + benchmarkRandomSet(b, 32) +} + +func BenchmarkIteratorOnParentWith1MDeletes(b *testing.B) { + benchmarkIteratorOnParentWithManyDeletes(b, 1_000_000) +} diff --git a/store/cachekv/store_test.go b/store/cachekv/store_test.go new file mode 100644 index 0000000000..4527585dcc --- /dev/null +++ b/store/cachekv/store_test.go @@ -0,0 +1,707 @@ +package cachekv_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + tmrand "github.com/tendermint/tendermint/libs/rand" + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/evmos/ethermint/store/cachekv" +) + +func newCacheKVStore() types.CacheKVStore { + // create two layer of cache store to better emulate the real world. + mem := dbadapter.Store{DB: dbm.NewMemDB()} + deliverState := cachekv.NewStore(mem) + return deliverState.Clone() +} + +func keyFmt(i int) []byte { return bz(fmt.Sprintf("key%0.8d", i)) } +func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) } + +func TestCacheKVStore(t *testing.T) { + mem := dbadapter.Store{DB: dbm.NewMemDB()} + st := cachekv.NewStore(mem) + + require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") + + // put something in mem and in cache + mem.Set(keyFmt(1), valFmt(1)) + st.Set(keyFmt(1), valFmt(1)) + require.Equal(t, valFmt(1), st.Get(keyFmt(1))) + + // update it in cache, shoudn't change mem + st.Set(keyFmt(1), valFmt(2)) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + require.Equal(t, valFmt(1), mem.Get(keyFmt(1))) + + // write it. should change mem + st.Write() + require.Equal(t, valFmt(2), mem.Get(keyFmt(1))) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + + // more writes and checks + st.Write() + st.Write() + require.Equal(t, valFmt(2), mem.Get(keyFmt(1))) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + + // make a new one, check it + st = cachekv.NewStore(mem) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + + // make a new one and delete - should not be removed from mem + st = cachekv.NewStore(mem) + st.Delete(keyFmt(1)) + require.Empty(t, st.Get(keyFmt(1))) + require.Equal(t, mem.Get(keyFmt(1)), valFmt(2)) + + // Write. should now be removed from both + st.Write() + require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") + require.Empty(t, mem.Get(keyFmt(1)), "Expected `key1` to be empty") +} + +func TestCacheKVStoreNoNilSet(t *testing.T) { + mem := dbadapter.Store{DB: dbm.NewMemDB()} + st := cachekv.NewStore(mem) + require.Panics(t, func() { st.Set([]byte("key"), nil) }, "setting a nil value should panic") + require.Panics(t, func() { st.Set(nil, []byte("value")) }, "setting a nil key should panic") + require.Panics(t, func() { st.Set([]byte(""), []byte("value")) }, "setting an empty key should panic") +} + +func TestCacheKVStoreNested(t *testing.T) { + mem := dbadapter.Store{DB: dbm.NewMemDB()} + st := cachekv.NewStore(mem) + + // set. check its there on st and not on mem. + st.Set(keyFmt(1), valFmt(1)) + require.Empty(t, mem.Get(keyFmt(1))) + require.Equal(t, valFmt(1), st.Get(keyFmt(1))) + + // make a new from st and check + st2 := cachekv.NewStore(st) + require.Equal(t, valFmt(1), st2.Get(keyFmt(1))) + + // update the value on st2, check it only effects st2 + st2.Set(keyFmt(1), valFmt(3)) + require.Equal(t, []byte(nil), mem.Get(keyFmt(1))) + require.Equal(t, valFmt(1), st.Get(keyFmt(1))) + require.Equal(t, valFmt(3), st2.Get(keyFmt(1))) + + // st2 writes to its parent, st. doesnt effect mem + st2.Write() + require.Equal(t, []byte(nil), mem.Get(keyFmt(1))) + require.Equal(t, valFmt(3), st.Get(keyFmt(1))) + + // updates mem + st.Write() + require.Equal(t, valFmt(3), mem.Get(keyFmt(1))) +} + +func TestCacheKVIteratorBounds(t *testing.T) { + st := newCacheKVStore() + + // set some items + nItems := 5 + for i := 0; i < nItems; i++ { + st.Set(keyFmt(i), valFmt(i)) + } + + // iterate over all of them + itr := st.Iterator(nil, nil) + i := 0 + for ; itr.Valid(); itr.Next() { + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) + i++ + } + require.Equal(t, nItems, i) + require.NoError(t, itr.Close()) + + // iterate over none + itr = st.Iterator(bz("money"), nil) + i = 0 + for ; itr.Valid(); itr.Next() { + i++ + } + require.Equal(t, 0, i) + require.NoError(t, itr.Close()) + + // iterate over lower + itr = st.Iterator(keyFmt(0), keyFmt(3)) + i = 0 + for ; itr.Valid(); itr.Next() { + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) + i++ + } + require.Equal(t, 3, i) + require.NoError(t, itr.Close()) + + // iterate over upper + itr = st.Iterator(keyFmt(2), keyFmt(4)) + i = 2 + for ; itr.Valid(); itr.Next() { + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) + i++ + } + require.Equal(t, 4, i) + require.NoError(t, itr.Close()) +} + +func TestCacheKVReverseIteratorBounds(t *testing.T) { + st := newCacheKVStore() + + // set some items + nItems := 5 + for i := 0; i < nItems; i++ { + st.Set(keyFmt(i), valFmt(i)) + } + + // iterate over all of them + itr := st.ReverseIterator(nil, nil) + i := 0 + for ; itr.Valid(); itr.Next() { + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(nItems-1-i), k) + require.Equal(t, valFmt(nItems-1-i), v) + i++ + } + require.Equal(t, nItems, i) + require.NoError(t, itr.Close()) + + // iterate over none + itr = st.ReverseIterator(bz("money"), nil) + i = 0 + for ; itr.Valid(); itr.Next() { + i++ + } + require.Equal(t, 0, i) + require.NoError(t, itr.Close()) + + // iterate over lower + end := 3 + itr = st.ReverseIterator(keyFmt(0), keyFmt(end)) + i = 0 + for ; itr.Valid(); itr.Next() { + i++ + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(end-i), k) + require.Equal(t, valFmt(end-i), v) + } + require.Equal(t, 3, i) + require.NoError(t, itr.Close()) + + // iterate over upper + end = 4 + itr = st.ReverseIterator(keyFmt(2), keyFmt(end)) + i = 0 + for ; itr.Valid(); itr.Next() { + i++ + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(end-i), k) + require.Equal(t, valFmt(end-i), v) + } + require.Equal(t, 2, i) + require.NoError(t, itr.Close()) +} + +func TestCacheKVMergeIteratorBasics(t *testing.T) { + st := newCacheKVStore() + + // set and delete an item in the cache, iterator should be empty + k, v := keyFmt(0), valFmt(0) + st.Set(k, v) + st.Delete(k) + assertIterateDomain(t, st, 0) + + // now set it and assert its there + st.Set(k, v) + assertIterateDomain(t, st, 1) + + // write it and assert its there + st.Write() + assertIterateDomain(t, st, 1) + + // remove it in cache and assert its not + st.Delete(k) + assertIterateDomain(t, st, 0) + + // write the delete and assert its not there + st.Write() + assertIterateDomain(t, st, 0) + + // add two keys and assert theyre there + k1, v1 := keyFmt(1), valFmt(1) + st.Set(k, v) + st.Set(k1, v1) + assertIterateDomain(t, st, 2) + + // write it and assert theyre there + st.Write() + assertIterateDomain(t, st, 2) + + // remove one in cache and assert its not + st.Delete(k1) + assertIterateDomain(t, st, 1) + + // write the delete and assert its not there + st.Write() + assertIterateDomain(t, st, 1) + + // delete the other key in cache and asserts its empty + st.Delete(k) + assertIterateDomain(t, st, 0) +} + +func TestCacheKVMergeIteratorDeleteLast(t *testing.T) { + st := newCacheKVStore() + + // set some items and write them + nItems := 5 + for i := 0; i < nItems; i++ { + st.Set(keyFmt(i), valFmt(i)) + } + st.Write() + + // set some more items and leave dirty + for i := nItems; i < nItems*2; i++ { + st.Set(keyFmt(i), valFmt(i)) + } + + // iterate over all of them + assertIterateDomain(t, st, nItems*2) + + // delete them all + for i := 0; i < nItems*2; i++ { + last := nItems*2 - 1 - i + st.Delete(keyFmt(last)) + assertIterateDomain(t, st, last) + } +} + +func TestCacheKVMergeIteratorDeletes(t *testing.T) { + st := newCacheKVStore() + truth := dbm.NewMemDB() + + // set some items and write them + nItems := 10 + for i := 0; i < nItems; i++ { + doOp(t, st, truth, opSet, i) + } + st.Write() + + // delete every other item, starting from 0 + for i := 0; i < nItems; i += 2 { + doOp(t, st, truth, opDel, i) + assertIterateDomainCompare(t, st, truth) + } + + // reset + st = newCacheKVStore() + truth = dbm.NewMemDB() + + // set some items and write them + for i := 0; i < nItems; i++ { + doOp(t, st, truth, opSet, i) + } + st.Write() + + // delete every other item, starting from 1 + for i := 1; i < nItems; i += 2 { + doOp(t, st, truth, opDel, i) + assertIterateDomainCompare(t, st, truth) + } +} + +func TestCacheKVMergeIteratorChunks(t *testing.T) { + st := newCacheKVStore() + + // Use the truth to check values on the merge iterator + truth := dbm.NewMemDB() + + // sets to the parent + setRange(t, st, truth, 0, 20) + setRange(t, st, truth, 40, 60) + st.Write() + + // sets to the cache + setRange(t, st, truth, 20, 40) + setRange(t, st, truth, 60, 80) + assertIterateDomainCheck(t, st, truth, []keyRange{{0, 80}}) + + // remove some parents and some cache + deleteRange(t, st, truth, 15, 25) + assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 80}}) + + // remove some parents and some cache + deleteRange(t, st, truth, 35, 45) + assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 35}, {45, 80}}) + + // write, add more to the cache, and delete some cache + st.Write() + setRange(t, st, truth, 38, 42) + deleteRange(t, st, truth, 40, 43) + assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 35}, {38, 40}, {45, 80}}) +} + +func TestCacheKVMergeIteratorDomain(t *testing.T) { + st := newCacheKVStore() + + itr := st.Iterator(nil, nil) + start, end := itr.Domain() + require.Equal(t, start, end) + require.NoError(t, itr.Close()) + + itr = st.Iterator(keyFmt(40), keyFmt(60)) + start, end = itr.Domain() + require.Equal(t, keyFmt(40), start) + require.Equal(t, keyFmt(60), end) + require.NoError(t, itr.Close()) + + start, end = st.ReverseIterator(keyFmt(0), keyFmt(80)).Domain() + require.Equal(t, keyFmt(0), start) + require.Equal(t, keyFmt(80), end) +} + +func TestCacheKVMergeIteratorRandom(t *testing.T) { + st := newCacheKVStore() + truth := dbm.NewMemDB() + + start, end := 25, 975 + max := 1000 + setRange(t, st, truth, start, end) + + // do an op, test the iterator + for i := 0; i < 2000; i++ { + doRandomOp(t, st, truth, max) + assertIterateDomainCompare(t, st, truth) + } +} + +func TestNilEndIterator(t *testing.T) { + const SIZE = 3000 + + tests := []struct { + name string + write bool + startIndex int + end []byte + }{ + {name: "write=false, end=nil", write: false, end: nil, startIndex: 1000}, + {name: "write=false, end=nil; full key scan", write: false, end: nil, startIndex: 2000}, + {name: "write=true, end=nil", write: true, end: nil, startIndex: 1000}, + {name: "write=false, end=non-nil", write: false, end: keyFmt(3000), startIndex: 1000}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + st := newCacheKVStore() + + for i := 0; i < SIZE; i++ { + kstr := keyFmt(i) + st.Set(kstr, valFmt(i)) + } + + if tt.write { + st.Write() + } + + itr := st.Iterator(keyFmt(tt.startIndex), tt.end) + i := tt.startIndex + j := 0 + for itr.Valid() { + require.Equal(t, keyFmt(i), itr.Key()) + require.Equal(t, valFmt(i), itr.Value()) + itr.Next() + i++ + j++ + } + + require.Equal(t, SIZE-tt.startIndex, j) + require.NoError(t, itr.Close()) + }) + } +} + +// TestIteratorDeadlock demonstrate the deadlock issue in cache store. +func TestIteratorDeadlock(t *testing.T) { + mem := dbadapter.Store{DB: dbm.NewMemDB()} + store := cachekv.NewStore(mem) + // the channel buffer is 64 and received once, so put at least 66 elements. + for i := 0; i < 66; i++ { + store.Set([]byte(fmt.Sprintf("key%d", i)), []byte{1}) + } + it := store.Iterator(nil, nil) + defer it.Close() + store.Set([]byte("key20"), []byte{1}) + // it'll be blocked here with previous version, or enable lock on btree. + it2 := store.Iterator(nil, nil) + defer it2.Close() +} + +//------------------------------------------------------------------------------------------- +// do some random ops + +const ( + opSet = 0 + opSetRange = 1 + opDel = 2 + opDelRange = 3 + opWrite = 4 + + totalOps = 5 // number of possible operations +) + +func randInt(n int) int { + return tmrand.NewRand().Int() % n +} + +// useful for replaying a error case if we find one +func doOp(t *testing.T, st types.CacheKVStore, truth dbm.DB, op int, args ...int) { + switch op { + case opSet: + k := args[0] + st.Set(keyFmt(k), valFmt(k)) + err := truth.Set(keyFmt(k), valFmt(k)) + require.NoError(t, err) + case opSetRange: + start := args[0] + end := args[1] + setRange(t, st, truth, start, end) + case opDel: + k := args[0] + st.Delete(keyFmt(k)) + err := truth.Delete(keyFmt(k)) + require.NoError(t, err) + case opDelRange: + start := args[0] + end := args[1] + deleteRange(t, st, truth, start, end) + case opWrite: + st.Write() + } +} + +func doRandomOp(t *testing.T, st types.CacheKVStore, truth dbm.DB, maxKey int) { + r := randInt(totalOps) + switch r { + case opSet: + k := randInt(maxKey) + st.Set(keyFmt(k), valFmt(k)) + err := truth.Set(keyFmt(k), valFmt(k)) + require.NoError(t, err) + case opSetRange: + start := randInt(maxKey - 2) + end := randInt(maxKey-start) + start + setRange(t, st, truth, start, end) + case opDel: + k := randInt(maxKey) + st.Delete(keyFmt(k)) + err := truth.Delete(keyFmt(k)) + require.NoError(t, err) + case opDelRange: + start := randInt(maxKey - 2) + end := randInt(maxKey-start) + start + deleteRange(t, st, truth, start, end) + case opWrite: + st.Write() + } +} + +//------------------------------------------------------------------------------------------- + +// iterate over whole domain +func assertIterateDomain(t *testing.T, st types.KVStore, expectedN int) { + itr := st.Iterator(nil, nil) + i := 0 + for ; itr.Valid(); itr.Next() { + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) + i++ + } + require.Equal(t, expectedN, i) + require.NoError(t, itr.Close()) +} + +func assertIterateDomainCheck(t *testing.T, st types.KVStore, mem dbm.DB, r []keyRange) { + // iterate over each and check they match the other + itr := st.Iterator(nil, nil) + itr2, err := mem.Iterator(nil, nil) // ground truth + require.NoError(t, err) + + krc := newKeyRangeCounter(r) + i := 0 + + for ; krc.valid(); krc.next() { + require.True(t, itr.Valid()) + require.True(t, itr2.Valid()) + + // check the key/val matches the ground truth + k, v := itr.Key(), itr.Value() + k2, v2 := itr2.Key(), itr2.Value() + require.Equal(t, k, k2) + require.Equal(t, v, v2) + + // check they match the counter + require.Equal(t, k, keyFmt(krc.key())) + + itr.Next() + itr2.Next() + i++ + } + + require.False(t, itr.Valid()) + require.False(t, itr2.Valid()) + require.NoError(t, itr.Close()) + require.NoError(t, itr2.Close()) +} + +func assertIterateDomainCompare(t *testing.T, st types.KVStore, mem dbm.DB) { + // iterate over each and check they match the other + itr := st.Iterator(nil, nil) + itr2, err := mem.Iterator(nil, nil) // ground truth + require.NoError(t, err) + checkIterators(t, itr, itr2) + checkIterators(t, itr2, itr) + require.NoError(t, itr.Close()) + require.NoError(t, itr2.Close()) +} + +func checkIterators(t *testing.T, itr, itr2 types.Iterator) { + for ; itr.Valid(); itr.Next() { + require.True(t, itr2.Valid()) + k, v := itr.Key(), itr.Value() + k2, v2 := itr2.Key(), itr2.Value() + require.Equal(t, k, k2) + require.Equal(t, v, v2) + itr2.Next() + } + require.False(t, itr.Valid()) + require.False(t, itr2.Valid()) +} + +//-------------------------------------------------------- + +func setRange(t *testing.T, st types.KVStore, mem dbm.DB, start, end int) { + for i := start; i < end; i++ { + st.Set(keyFmt(i), valFmt(i)) + err := mem.Set(keyFmt(i), valFmt(i)) + require.NoError(t, err) + } +} + +func deleteRange(t *testing.T, st types.KVStore, mem dbm.DB, start, end int) { + for i := start; i < end; i++ { + st.Delete(keyFmt(i)) + err := mem.Delete(keyFmt(i)) + require.NoError(t, err) + } +} + +//-------------------------------------------------------- + +type keyRange struct { + start int + end int +} + +func (kr keyRange) len() int { + return kr.end - kr.start +} + +func newKeyRangeCounter(kr []keyRange) *keyRangeCounter { + return &keyRangeCounter{keyRanges: kr} +} + +// we can iterate over this and make sure our real iterators have all the right keys +type keyRangeCounter struct { + rangeIdx int + idx int + keyRanges []keyRange +} + +func (krc *keyRangeCounter) valid() bool { + maxRangeIdx := len(krc.keyRanges) - 1 + maxRange := krc.keyRanges[maxRangeIdx] + + // if we're not in the max range, we're valid + if krc.rangeIdx <= maxRangeIdx && + krc.idx < maxRange.len() { + return true + } + + return false +} + +func (krc *keyRangeCounter) next() { + thisKeyRange := krc.keyRanges[krc.rangeIdx] + if krc.idx == thisKeyRange.len()-1 { + krc.rangeIdx++ + krc.idx = 0 + } else { + krc.idx++ + } +} + +func (krc *keyRangeCounter) key() int { + thisKeyRange := krc.keyRanges[krc.rangeIdx] + return thisKeyRange.start + krc.idx +} + +//-------------------------------------------------------- + +func bz(s string) []byte { return []byte(s) } + +func BenchmarkCacheKVStoreGetNoKeyFound(b *testing.B) { + b.ReportAllocs() + st := newCacheKVStore() + b.ResetTimer() + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}) + } +} + +func BenchmarkCacheKVStoreGetKeyFound(b *testing.B) { + b.ReportAllocs() + st := newCacheKVStore() + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + st.Set(arr, arr) + } + b.ResetTimer() + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}) + } +} + +//-------------------------------------------------------- + +func BenchmarkCacheKVStoreSetAndCommit(b *testing.B) { + mem := dbadapter.Store{DB: dbm.NewMemDB()} + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + store := cachekv.NewStore(mem) + store1 := store.Clone() + for j := 0; j < 10; j++ { + store1.Set(sdk.Uint64ToBigEndian(uint64(i+j)), []byte{byte(i)}) + } + store.Restore(store1) + store.Write() + } +} diff --git a/store/cachemulti/store.go b/store/cachemulti/store.go new file mode 100644 index 0000000000..43f64da39f --- /dev/null +++ b/store/cachemulti/store.go @@ -0,0 +1,191 @@ +package cachemulti + +import ( + "fmt" + "io" + + "github.com/cosmos/cosmos-sdk/store/tracekv" + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/evmos/ethermint/store/cachekv" +) + +// storeNameCtxKey is the TraceContext metadata key that identifies +// the store which emitted a given trace. +const storeNameCtxKey = "store_name" + +//---------------------------------------- +// Store + +// Store holds many branched stores. +// Implements MultiStore. +// NOTE: a Store (and MultiStores in general) should never expose the +// keys for the substores. +type Store struct { + stores map[types.StoreKey]*cachekv.Store + + traceWriter io.Writer + traceContext types.TraceContext +} + +var _ types.CacheMultiStore = Store{} + +// NewFromKVStore creates a new Store object from a mapping of store keys to +// CacheWrapper objects and a KVStore as the database. Each CacheWrapper store +// is a branched store. +func NewFromKVStore( + stores map[types.StoreKey]types.KVStore, + traceWriter io.Writer, traceContext types.TraceContext, +) Store { + cms := Store{ + stores: make(map[types.StoreKey]*cachekv.Store, len(stores)), + traceWriter: traceWriter, + traceContext: traceContext, + } + + for key, store := range stores { + if cms.TracingEnabled() { + tctx := cms.traceContext.Clone().Merge(types.TraceContext{ + storeNameCtxKey: key.Name(), + }) + + store = tracekv.NewStore(store, cms.traceWriter, tctx) + } + cms.stores[key] = cachekv.NewStore(store) + } + + return cms +} + +// NewStore creates a new Store object from parent rootmulti store, it branch out inner store of the specified keys. +func NewStore( + parent types.MultiStore, keys map[string]*types.KVStoreKey, +) Store { + stores := make(map[types.StoreKey]types.KVStore, len(keys)) + for _, key := range keys { + stores[key] = parent.GetKVStore(key) + } + return NewFromKVStore(stores, nil, nil) +} + +func newCacheMultiStoreFromCMS(cms Store) Store { + stores := make(map[types.StoreKey]types.KVStore) + for k, v := range cms.stores { + stores[k] = v + } + + return NewFromKVStore(stores, cms.traceWriter, cms.traceContext) +} + +// SetTracer sets the tracer for the MultiStore that the underlying +// stores will utilize to trace operations. A MultiStore is returned. +func (cms Store) SetTracer(w io.Writer) types.MultiStore { + cms.traceWriter = w + return cms +} + +// SetTracingContext updates the tracing context for the MultiStore by merging +// the given context with the existing context by key. Any existing keys will +// be overwritten. It is implied that the caller should update the context when +// necessary between tracing operations. It returns a modified MultiStore. +func (cms Store) SetTracingContext(tc types.TraceContext) types.MultiStore { + if cms.traceContext != nil { + for k, v := range tc { + cms.traceContext[k] = v + } + } else { + cms.traceContext = tc + } + + return cms +} + +// TracingEnabled returns if tracing is enabled for the MultiStore. +func (cms Store) TracingEnabled() bool { + return cms.traceWriter != nil +} + +// LatestVersion returns the branch version of the store +func (cms Store) LatestVersion() int64 { + panic("cannot get latest version from branch cached multi-store") +} + +// GetStoreType returns the type of the store. +func (cms Store) GetStoreType() types.StoreType { + return types.StoreTypeMulti +} + +// Write calls Write on each underlying store. +func (cms Store) Write() { + for _, store := range cms.stores { + store.Write() + } +} + +// Clone creates a snapshot of each store of the cache-multistore. +// Each copy is a copy-on-write operation and therefore is very fast. +func (cms Store) Clone() types.CacheMultiStore { + stores := make(map[types.StoreKey]*cachekv.Store, len(cms.stores)) + for key, store := range cms.stores { + stores[key] = store.Clone() + } + return Store{ + stores: stores, + + traceWriter: cms.traceWriter, + traceContext: cms.traceContext, + } +} + +// Restore restores the cache-multistore cache to a given snapshot. +func (cms Store) Restore(s types.CacheMultiStore) { + ms := s.(Store) + for key, store := range cms.stores { + otherStore, ok := ms.stores[key] + if !ok { + panic("Invariant violation: Restore should only be called on a store cloned from itself") + } + store.Restore(otherStore) + } +} + +// Implements CacheWrapper. +func (cms Store) CacheWrap() types.CacheWrap { + return cms.CacheMultiStore().(types.CacheWrap) +} + +// CacheWrapWithTrace implements the CacheWrapper interface. +func (cms Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap { + return cms.CacheWrap() +} + +// Implements MultiStore. +func (cms Store) CacheMultiStore() types.CacheMultiStore { + return newCacheMultiStoreFromCMS(cms) +} + +// CacheMultiStoreWithVersion implements the MultiStore interface. It will panic +// as an already cached multi-store cannot load previous versions. +// +// TODO: The store implementation can possibly be modified to support this as it +// seems safe to load previous versions (heights). +func (cms Store) CacheMultiStoreWithVersion(_ int64) (types.CacheMultiStore, error) { + panic("cannot branch cached multi-store with a version") +} + +// GetStore returns an underlying Store by key. +func (cms Store) GetStore(key types.StoreKey) types.Store { + s := cms.stores[key] + if key == nil || s == nil { + panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) + } + return types.Store(s) +} + +// GetKVStore returns an underlying KVStore by key. +func (cms Store) GetKVStore(key types.StoreKey) types.KVStore { + store := cms.stores[key] + if key == nil || store == nil { + panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) + } + return types.KVStore(store) +} diff --git a/store/cachemulti/store_test.go b/store/cachemulti/store_test.go new file mode 100644 index 0000000000..80d54a1f43 --- /dev/null +++ b/store/cachemulti/store_test.go @@ -0,0 +1,25 @@ +package cachemulti + +import ( + "fmt" + "testing" + + "github.com/evmos/ethermint/store/cachekv" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/store/types" +) + +func TestStoreGetKVStore(t *testing.T) { + require := require.New(t) + + s := Store{stores: map[types.StoreKey]*cachekv.Store{}} + key := types.NewKVStoreKey("abc") + errMsg := fmt.Sprintf("kv store with key %v has not been registered in stores", key) + + require.PanicsWithValue(errMsg, + func() { s.GetStore(key) }) + + require.PanicsWithValue(errMsg, + func() { s.GetKVStore(key) }) +} diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 2d3e90a175..5320644079 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -44,8 +44,7 @@ type Keeper struct { // Store key required for the EVM Prefix KVStore. It is required by: // - storing account's Storage State // - storing account's Code - // - storing transaction Logs - // - storing Bloom filters by block height. Needed for the Web3 API. + // - storing module parameters storeKey storetypes.StoreKey // key to access the transient store, which is reset on every block during Commit @@ -78,6 +77,10 @@ type Keeper struct { evmConstructor evm.Constructor // Legacy subspace ss paramstypes.Subspace + + // a set of store keys that should cover all the precompile use cases, + // or ideally just pass the application's all stores. + keys map[string]*storetypes.KVStoreKey } // NewKeeper generates new evm module keeper @@ -93,6 +96,7 @@ func NewKeeper( evmConstructor evm.Constructor, tracer string, ss paramstypes.Subspace, + keys map[string]*storetypes.KVStoreKey, ) *Keeper { // ensure evm module account is set if addr := ak.GetModuleAddress(types.ModuleName); addr == nil { @@ -118,6 +122,7 @@ func NewKeeper( evmConstructor: evmConstructor, tracer: tracer, ss: ss, + keys: keys, } } diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 6b98d3c97e..f6e5d416d2 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -329,7 +329,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, return nil, errorsmod.Wrap(types.ErrCallDisabled, "failed to call contract") } - stateDB := statedb.New(ctx, k, txConfig) + stateDB := statedb.New(ctx, k.keys, k, txConfig) evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB) leftoverGas := msg.Gas() @@ -400,12 +400,9 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, // The dirty states in `StateDB` is either committed or discarded after return if commit { - dbCtx, err := stateDB.Commit() - if err != nil { + if err := stateDB.Commit(); err != nil { return nil, errorsmod.Wrap(err, "failed to commit stateDB") } - - ctx.MultiStore().Restore(dbCtx.MultiStore()) } // calculate a minimum amount of gas to be charged to sender if GasLimit diff --git a/x/evm/statedb/native.go b/x/evm/statedb/native.go index 8aa1427b4d..0178984624 100644 --- a/x/evm/statedb/native.go +++ b/x/evm/statedb/native.go @@ -8,7 +8,7 @@ import ( var _ JournalEntry = nativeChange{} type nativeChange struct { - snapshot types.CacheMultiStore + snapshot types.MultiStore } func (native nativeChange) Dirtied() *common.Address { diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 03e71095e6..44246fae4e 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -21,11 +21,13 @@ import ( "sort" errorsmod "cosmossdk.io/errors" + storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/evmos/ethermint/store/cachemulti" ) // revision is the identifier of a version of state. @@ -67,8 +69,10 @@ type StateDB struct { accessList *accessList } -// New creates a new state from a given trie. -func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { +// New creates a new state from a given trie, StateDB will branch out stores for the specified keys to support +// precompiles. +func New(ctx sdk.Context, keys map[string]*storetypes.KVStoreKey, keeper Keeper, txConfig TxConfig) *StateDB { + ctx = ctx.WithMultiStore(cachemulti.NewStore(ctx.MultiStore(), keys)) return &StateDB{ keeper: keeper, ctx: ctx, @@ -80,6 +84,13 @@ func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { } } +// CacheMultiStore cast the multistore to *cachemulti.Store. +// invariant: the multistore must be a `cachemulti.Store`, +// prove: it's set in constructor and never overridden. +func (s *StateDB) CacheMultiStore() cachemulti.Store { + return s.ctx.MultiStore().(cachemulti.Store) +} + // Keeper returns the underlying `Keeper` func (s *StateDB) Keeper() Keeper { return s.keeper @@ -298,12 +309,15 @@ func (s *StateDB) setStateObject(object *stateObject) { s.stateObjects[object.Address()] = object } -func (s *StateDB) restoreNativeState(ms sdk.CacheMultiStore) { +func (s *StateDB) restoreNativeState(ms sdk.MultiStore) { s.ctx = s.ctx.WithMultiStore(ms) } -func (s *StateDB) executeNativeAction(action func(ctx sdk.Context) error) error { - snapshot := s.ctx.MultiStore().Clone() +// ExecuteNativeAction executes native action in isolate, +// the writes will be revert when either the native action itself fail +// or the wrapping message call reverted. +func (s *StateDB) ExecuteNativeAction(action func(ctx sdk.Context) error) error { + snapshot := s.CacheMultiStore().Clone() err := action(s.ctx) if err != nil { s.restoreNativeState(snapshot) @@ -465,19 +479,23 @@ func (s *StateDB) RevertToSnapshot(revid int) { // Commit writes the dirty states to keeper // the StateDB object should be discarded after committed. -func (s *StateDB) Commit() (sdk.Context, error) { +func (s *StateDB) Commit() error { + // commit the native cache store first, + // the states managed by precompiles and the other part of StateDB must not overlap. + s.CacheMultiStore().Write() + for _, addr := range s.journal.sortedDirties() { obj := s.stateObjects[addr] if obj.suicided { if err := s.keeper.DeleteAccount(s.ctx, obj.Address()); err != nil { - return s.ctx, errorsmod.Wrap(err, "failed to delete account") + return errorsmod.Wrap(err, "failed to delete account") } } else { if obj.code != nil && obj.dirtyCode { s.keeper.SetCode(s.ctx, obj.CodeHash(), obj.code) } if err := s.keeper.SetAccount(s.ctx, obj.Address(), obj.account); err != nil { - return s.ctx, errorsmod.Wrap(err, "failed to set account") + return errorsmod.Wrap(err, "failed to set account") } for _, key := range obj.dirtyStorage.SortedKeys() { value := obj.dirtyStorage[key] @@ -489,5 +507,5 @@ func (s *StateDB) Commit() (sdk.Context, error) { } } } - return s.ctx, nil + return nil } From 605c757e4d7b27f803916f8363bdaacb0fbad05b Mon Sep 17 00:00:00 2001 From: yihuang Date: Fri, 17 Feb 2023 10:59:17 +0800 Subject: [PATCH 22/41] Update x/evm/statedb/statedb.go --- x/evm/statedb/statedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 44246fae4e..b533aced24 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -86,7 +86,7 @@ func New(ctx sdk.Context, keys map[string]*storetypes.KVStoreKey, keeper Keeper, // CacheMultiStore cast the multistore to *cachemulti.Store. // invariant: the multistore must be a `cachemulti.Store`, -// prove: it's set in constructor and never overridden. +// prove: it's set in constructor and only modified in `restoreNativeState` which keeps the invariant. func (s *StateDB) CacheMultiStore() cachemulti.Store { return s.ctx.MultiStore().(cachemulti.Store) } From 67c926b64ca339446bb78e2fe9e513342884bb0a Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 20 Feb 2023 09:36:28 +0800 Subject: [PATCH 23/41] transfer crc20 <-> native token --- .../hardhat/contracts/TestBank.sol | 8 ++- tests/integration_tests/test_precompiles.py | 57 +++++++++++++------ x/evm/keeper/precompiles/bank.go | 56 +++++++++++++++++- 3 files changed, 99 insertions(+), 22 deletions(-) diff --git a/tests/integration_tests/hardhat/contracts/TestBank.sol b/tests/integration_tests/hardhat/contracts/TestBank.sol index 7ca047bc7e..50a148f859 100644 --- a/tests/integration_tests/hardhat/contracts/TestBank.sol +++ b/tests/integration_tests/hardhat/contracts/TestBank.sol @@ -4,7 +4,7 @@ pragma solidity >0.6.6; contract TestBank { address constant bankContract = 0x0000000000000000000000000000000000000064; function nativeMint(uint256 amount) public { - (bool result, bytes memory _data) = bankContract.call(abi.encodeWithSignature( + (bool result, ) = bankContract.call(abi.encodeWithSignature( "mint(address,uint256)", msg.sender, amount )); require(result, "native call"); @@ -20,4 +20,10 @@ contract TestBank { nativeMint(amount); revert("test"); } + function nativeTransfer(address recipient, uint256 amount) public { + (bool result, ) = bankContract.call(abi.encodeWithSignature( + "transfer(address,address,uint256)", msg.sender, recipient, amount + )); + require(result, "native transfer"); + } } \ No newline at end of file diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index fe32797ea0..d377c13707 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -1,6 +1,7 @@ from .utils import ( ADDRS, CONTRACTS, + KEYS, deploy_contract, eth_to_bech32, send_transaction, @@ -13,32 +14,52 @@ def get_balance(cli, addr, denom): def test_call(ethermint): w3 = ethermint.w3 - addr = ADDRS["validator"] + cli = ethermint.cosmos_cli() + native_denom = "aphoton" + sender = ADDRS["signer1"] + keys = KEYS["signer1"] amount = 100 contract, _ = deploy_contract(w3, CONTRACTS["TestBank"]) - tx = contract.functions.nativeMint(amount).build_transaction({"from": addr}) - receipt = send_transaction(w3, tx) - assert receipt.status == 1, "expect success" - - # query balance through contract - assert contract.caller.nativeBalanceOf(addr) == amount - # query balance through cosmos rpc - cli = ethermint.cosmos_cli() denom = "evm/" + contract.address - assert get_balance(cli, addr, denom) == amount + + def assert_sender_balance(tx, expect_status, amount): + balance = get_balance(cli, sender, native_denom) + receipt = send_transaction(w3, tx, keys) + assert receipt.status == expect_status + fee = receipt["cumulativeGasUsed"] * receipt["effectiveGasPrice"] + current = get_balance(cli, sender, native_denom) + assert balance == current + fee + amount + + def assert_crc20_balance(address, amt): + # query balance through contract + assert contract.caller.nativeBalanceOf(address) == amt + # query balance through cosmos rpc + assert get_balance(cli, address, denom) == amt + + # test mint + tx = contract.functions.nativeMint(amount).build_transaction({"from": sender}) + assert_sender_balance(tx, 1, amount) + assert_crc20_balance(sender, amount) # test exception revert tx = contract.functions.nativeMintRevert(amount).build_transaction( - {"from": addr, "gas": 210000} + {"from": sender, "gas": 210000} ) - receipt = send_transaction(w3, tx) - assert receipt.status == 0, "expect failure" - + assert_sender_balance(tx, 0, 0) # check balance don't change - assert contract.caller.nativeBalanceOf(addr) == amount - # query balance through cosmos rpc - cli = ethermint.cosmos_cli() - assert get_balance(cli, addr, denom) == amount + assert_crc20_balance(sender, amount) + + # test transfer + recipient = ADDRS["signer2"] + transfer_amt = 10 + recipient_balance = get_balance(cli, recipient, native_denom) + tx = contract.functions.nativeTransfer(recipient, transfer_amt).build_transaction( + {"from": sender} + ) + assert_sender_balance(tx, 1, transfer_amt) + assert_crc20_balance(sender, amount - transfer_amt) + assert get_balance(cli, recipient, native_denom) == recipient_balance + transfer_amt + assert_crc20_balance(recipient, transfer_amt) def test_delegate(ethermint): diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index 54e03cd5a3..776153049d 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -9,8 +9,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" + errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/evmos/ethermint/x/evm/types" ) @@ -19,6 +19,7 @@ const EVMDenomPrefix = "evm/" var ( MintMethod abi.Method BalanceOfMethod abi.Method + TransferMethod abi.Method ) func init() { @@ -47,6 +48,19 @@ func init() { Type: uint256Type, }}, ) + TransferMethod = abi.NewMethod( + "transfer", "transfer", abi.Function, "", false, false, abi.Arguments{abi.Argument{ + Name: "sender", + Type: addressType, + }, abi.Argument{ + Name: "recipient", + Type: addressType, + }, abi.Argument{ + Name: "amount", + Type: uint256Type, + }}, + nil, + ) } func EVMDenom(token common.Address) string { @@ -100,10 +114,17 @@ func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ( addr := sdk.AccAddress(recipient.Bytes()) amt := sdk.NewCoins(sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount))) if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, amt); err != nil { - return sdkerrors.Wrap(err, "fail to mint coins in precompiled contract") + return errorsmod.Wrap(err, "fail to mint coins in precompiled contract") } if err := bc.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, amt); err != nil { - return sdkerrors.Wrap(err, "fail to send mint coins to account") + return errorsmod.Wrap(err, "fail to send mint coins to account") + } + nativeAmt := sdk.NewCoins(sdk.NewCoin(types.DefaultEVMDenom, sdkmath.NewIntFromBigInt(amount))) + if err := bc.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, nativeAmt); err != nil { + return errorsmod.Wrap(err, "fail to send burn coins to module") + } + if err := bc.bankKeeper.BurnCoins(ctx, types.ModuleName, nativeAmt); err != nil { + return errorsmod.Wrap(err, "fail to burn coins in precompiled contract") } return nil }) @@ -120,6 +141,35 @@ func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ( // query from storage balance := bc.bankKeeper.GetBalance(bc.ctx, sdk.AccAddress(addr.Bytes()), EVMDenom(token)).Amount.BigInt() return BalanceOfMethod.Outputs.Pack(balance) + case string(TransferMethod.ID): + if readonly { + return nil, errors.New("the method is not readonly") + } + args, err := TransferMethod.Inputs.Unpack(contract.Input[4:]) + if err != nil { + return nil, errors.New("fail to unpack input arguments") + } + sender := args[0].(common.Address) + recipient := args[1].(common.Address) + amount := args[2].(*big.Int) + if amount.Sign() <= 0 { + return nil, errors.New("invalid amount") + } + err = bc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { + from := sdk.AccAddress(sender.Bytes()) + to := sdk.AccAddress(recipient.Bytes()) + nativeAmt := sdk.NewCoins(sdk.NewCoin(types.DefaultEVMDenom, sdkmath.NewIntFromBigInt(amount))) + if err := bc.bankKeeper.SendCoins(ctx, from, to, nativeAmt); err != nil { + return errorsmod.Wrap(err, "fail to send coins from sender to recipient") + } + denom := EVMDenom(contract.CallerAddress) + amt := sdk.NewCoins(sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount))) + if err := bc.bankKeeper.SendCoins(ctx, from, to, amt); err != nil { + return errorsmod.Wrap(err, "fail to send coins in precompiled contract") + } + return nil + }) + return nil, err default: return nil, errors.New("unknown method") } From bd38738f1b8b0504cf6d52e56b70497cbc6df5fb Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 20 Feb 2023 09:36:40 +0800 Subject: [PATCH 24/41] add burn --- .../hardhat/contracts/TestBank.sol | 6 +++ tests/integration_tests/test_precompiles.py | 34 +++++++++------ x/evm/keeper/precompiles/bank.go | 43 ++++++++++++++----- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/tests/integration_tests/hardhat/contracts/TestBank.sol b/tests/integration_tests/hardhat/contracts/TestBank.sol index 50a148f859..e7a6a2cc45 100644 --- a/tests/integration_tests/hardhat/contracts/TestBank.sol +++ b/tests/integration_tests/hardhat/contracts/TestBank.sol @@ -9,6 +9,12 @@ contract TestBank { )); require(result, "native call"); } + function nativeBurn(uint256 amount) public { + (bool result, ) = bankContract.call(abi.encodeWithSignature( + "burn(address,uint256)", msg.sender, amount + )); + require(result, "native call"); + } function nativeBalanceOf(address addr) public returns (uint256) { (bool result, bytes memory data) = bankContract.call(abi.encodeWithSignature( "balanceOf(address,address)", address(this), addr diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index d377c13707..607513d08b 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -18,17 +18,17 @@ def test_call(ethermint): native_denom = "aphoton" sender = ADDRS["signer1"] keys = KEYS["signer1"] - amount = 100 + amt1 = 100 contract, _ = deploy_contract(w3, CONTRACTS["TestBank"]) denom = "evm/" + contract.address - def assert_sender_balance(tx, expect_status, amount): + def assert_sender_balance(tx, expect_status, amt): balance = get_balance(cli, sender, native_denom) receipt = send_transaction(w3, tx, keys) assert receipt.status == expect_status fee = receipt["cumulativeGasUsed"] * receipt["effectiveGasPrice"] current = get_balance(cli, sender, native_denom) - assert balance == current + fee + amount + assert balance == current + fee + amt def assert_crc20_balance(address, amt): # query balance through contract @@ -37,29 +37,35 @@ def assert_crc20_balance(address, amt): assert get_balance(cli, address, denom) == amt # test mint - tx = contract.functions.nativeMint(amount).build_transaction({"from": sender}) - assert_sender_balance(tx, 1, amount) - assert_crc20_balance(sender, amount) + tx = contract.functions.nativeMint(amt1).build_transaction({"from": sender}) + assert_sender_balance(tx, 1, amt1) + assert_crc20_balance(sender, amt1) # test exception revert - tx = contract.functions.nativeMintRevert(amount).build_transaction( + tx = contract.functions.nativeMintRevert(amt1).build_transaction( {"from": sender, "gas": 210000} ) assert_sender_balance(tx, 0, 0) # check balance don't change - assert_crc20_balance(sender, amount) + assert_crc20_balance(sender, amt1) + + # test burn + amt2 = 50 + tx = contract.functions.nativeBurn(amt2).build_transaction({"from": sender}) + assert_sender_balance(tx, 1, -amt2) + assert_crc20_balance(sender, amt1 - amt2) # test transfer recipient = ADDRS["signer2"] - transfer_amt = 10 + amt3 = 10 recipient_balance = get_balance(cli, recipient, native_denom) - tx = contract.functions.nativeTransfer(recipient, transfer_amt).build_transaction( + tx = contract.functions.nativeTransfer(recipient, amt3).build_transaction( {"from": sender} ) - assert_sender_balance(tx, 1, transfer_amt) - assert_crc20_balance(sender, amount - transfer_amt) - assert get_balance(cli, recipient, native_denom) == recipient_balance + transfer_amt - assert_crc20_balance(recipient, transfer_amt) + assert_sender_balance(tx, 1, amt3) + assert_crc20_balance(sender, amt1 - amt2 - amt3) + assert get_balance(cli, recipient, native_denom) == recipient_balance + amt3 + assert_crc20_balance(recipient, amt3) def test_delegate(ethermint): diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index 776153049d..a7d6602d2f 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -18,6 +18,7 @@ const EVMDenomPrefix = "evm/" var ( MintMethod abi.Method + BurnMethod abi.Method BalanceOfMethod abi.Method TransferMethod abi.Method ) @@ -35,6 +36,16 @@ func init() { }}, nil, ) + BurnMethod = abi.NewMethod( + "burn", "burn", abi.Function, "", false, false, abi.Arguments{abi.Argument{ + Name: "recipient", + Type: addressType, + }, abi.Argument{ + Name: "amount", + Type: uint256Type, + }}, + nil, + ) BalanceOfMethod = abi.NewMethod( "balanceOf", "balanceOf", abi.Function, "", false, false, abi.Arguments{abi.Argument{ Name: "token", @@ -96,11 +107,22 @@ func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ( // parse input methodID := contract.Input[:4] switch string(methodID) { - case string(MintMethod.ID): + case string(MintMethod.ID), string(BurnMethod.ID): + var method abi.Method + var mintDenom, burnDenom string + if string(methodID) == string(MintMethod.ID) { + method = MintMethod + mintDenom = EVMDenom(contract.CallerAddress) + burnDenom = types.DefaultEVMDenom + } else { + method = BurnMethod + burnDenom = EVMDenom(contract.CallerAddress) + mintDenom = types.DefaultEVMDenom + } if readonly { return nil, errors.New("the method is not readonly") } - args, err := MintMethod.Inputs.Unpack(contract.Input[4:]) + args, err := method.Inputs.Unpack(contract.Input[4:]) if err != nil { return nil, errors.New("fail to unpack input arguments") } @@ -109,23 +131,22 @@ func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ( if amount.Sign() <= 0 { return nil, errors.New("invalid amount") } - denom := EVMDenom(contract.CallerAddress) err = bc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { addr := sdk.AccAddress(recipient.Bytes()) - amt := sdk.NewCoins(sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount))) - if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, amt); err != nil { - return errorsmod.Wrap(err, "fail to mint coins in precompiled contract") - } - if err := bc.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, amt); err != nil { - return errorsmod.Wrap(err, "fail to send mint coins to account") - } - nativeAmt := sdk.NewCoins(sdk.NewCoin(types.DefaultEVMDenom, sdkmath.NewIntFromBigInt(amount))) + nativeAmt := sdk.NewCoins(sdk.NewCoin(burnDenom, sdkmath.NewIntFromBigInt(amount))) if err := bc.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, nativeAmt); err != nil { return errorsmod.Wrap(err, "fail to send burn coins to module") } if err := bc.bankKeeper.BurnCoins(ctx, types.ModuleName, nativeAmt); err != nil { return errorsmod.Wrap(err, "fail to burn coins in precompiled contract") } + amt := sdk.NewCoins(sdk.NewCoin(mintDenom, sdkmath.NewIntFromBigInt(amount))) + if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, amt); err != nil { + return errorsmod.Wrap(err, "fail to mint coins in precompiled contract") + } + if err := bc.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, amt); err != nil { + return errorsmod.Wrap(err, "fail to send mint coins to account") + } return nil }) if err != nil { From 8fc3db726f848dc65f70033e2494edf384c8f5c3 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 20 Feb 2023 09:36:52 +0800 Subject: [PATCH 25/41] validate coins and recipient --- tests/integration_tests/test_precompiles.py | 29 ++++++++++-- tests/integration_tests/utils.py | 7 +++ x/evm/keeper/precompiles/bank.go | 52 +++++++++++++++------ x/evm/types/interfaces.go | 2 + 4 files changed, 71 insertions(+), 19 deletions(-) diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index 607513d08b..dc4abb1451 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -1,9 +1,13 @@ +import pytest +import web3 + from .utils import ( ADDRS, CONTRACTS, KEYS, deploy_contract, eth_to_bech32, + module_address, send_transaction, ) @@ -39,21 +43,23 @@ def assert_crc20_balance(address, amt): # test mint tx = contract.functions.nativeMint(amt1).build_transaction({"from": sender}) assert_sender_balance(tx, 1, amt1) - assert_crc20_balance(sender, amt1) + balance = amt1 + assert_crc20_balance(sender, balance) # test exception revert tx = contract.functions.nativeMintRevert(amt1).build_transaction( {"from": sender, "gas": 210000} ) - assert_sender_balance(tx, 0, 0) # check balance don't change - assert_crc20_balance(sender, amt1) + assert_sender_balance(tx, 0, 0) + assert_crc20_balance(sender, balance) # test burn amt2 = 50 tx = contract.functions.nativeBurn(amt2).build_transaction({"from": sender}) assert_sender_balance(tx, 1, -amt2) - assert_crc20_balance(sender, amt1 - amt2) + balance -= amt2 + assert_crc20_balance(sender, balance) # test transfer recipient = ADDRS["signer2"] @@ -63,10 +69,23 @@ def assert_crc20_balance(address, amt): {"from": sender} ) assert_sender_balance(tx, 1, amt3) - assert_crc20_balance(sender, amt1 - amt2 - amt3) + balance -= amt3 + assert_crc20_balance(sender, balance) assert get_balance(cli, recipient, native_denom) == recipient_balance + amt3 assert_crc20_balance(recipient, amt3) + # test transfer to blocked address + recipient = module_address("evm") + amt4 = 20 + recipient_balance = get_balance(cli, recipient, native_denom) + with pytest.raises(web3.exceptions.ContractLogicError): + tx = contract.functions.nativeTransfer(recipient, amt4).build_transaction( + {"from": sender} + ) + send_transaction(w3, tx, keys) + # check balance don't change + assert_crc20_balance(sender, balance) + def test_delegate(ethermint): w3 = ethermint.w3 diff --git a/tests/integration_tests/utils.py b/tests/integration_tests/utils.py index 5eb466b5de..2063c6ecbd 100644 --- a/tests/integration_tests/utils.py +++ b/tests/integration_tests/utils.py @@ -1,3 +1,4 @@ +import hashlib import json import os import socket @@ -10,6 +11,7 @@ from dateutil.parser import isoparse from dotenv import load_dotenv from eth_account import Account +from eth_utils import to_checksum_address from hexbytes import HexBytes from web3._utils.transactions import fill_nonce, fill_transaction_defaults from web3.exceptions import TimeExhausted @@ -202,3 +204,8 @@ def parse_events(logs): ev["type"]: {attr["key"]: attr["value"] for attr in ev["attributes"]} for ev in logs[0]["events"] } + + +def module_address(name): + data = hashlib.sha256(name.encode()).digest()[:20] + return to_checksum_address(decode_bech32(eth_to_bech32(data)).hex()) diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index a7d6602d2f..ac32705d38 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -11,6 +11,7 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/evmos/ethermint/x/evm/types" ) @@ -103,6 +104,17 @@ func (bc *BankContract) IsStateful() bool { return true } +func (bc *BankContract) checkBlockedAddr(addr sdk.AccAddress) error { + to, err := sdk.AccAddressFromBech32(addr.String()) + if err != nil { + return err + } + if bc.bankKeeper.BlockedAddr(to) { + return errorsmod.Wrapf(errortypes.ErrUnauthorized, "%s is not allowed to receive funds", to.String()) + } + return nil +} + func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { // parse input methodID := contract.Input[:4] @@ -131,20 +143,26 @@ func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ( if amount.Sign() <= 0 { return nil, errors.New("invalid amount") } + addr := sdk.AccAddress(recipient.Bytes()) + if err := bc.checkBlockedAddr(addr); err != nil { + return nil, err + } + nativeAmt := sdk.NewCoin(burnDenom, sdkmath.NewIntFromBigInt(amount)) + amt := sdk.NewCoin(mintDenom, sdkmath.NewIntFromBigInt(amount)) err = bc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { - addr := sdk.AccAddress(recipient.Bytes()) - nativeAmt := sdk.NewCoins(sdk.NewCoin(burnDenom, sdkmath.NewIntFromBigInt(amount))) - if err := bc.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, nativeAmt); err != nil { + if err := bc.bankKeeper.IsSendEnabledCoins(ctx, nativeAmt, amt); err != nil { + return err + } + if err := bc.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, sdk.NewCoins(nativeAmt)); err != nil { return errorsmod.Wrap(err, "fail to send burn coins to module") } - if err := bc.bankKeeper.BurnCoins(ctx, types.ModuleName, nativeAmt); err != nil { + if err := bc.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(nativeAmt)); err != nil { return errorsmod.Wrap(err, "fail to burn coins in precompiled contract") } - amt := sdk.NewCoins(sdk.NewCoin(mintDenom, sdkmath.NewIntFromBigInt(amount))) - if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, amt); err != nil { + if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amt)); err != nil { return errorsmod.Wrap(err, "fail to mint coins in precompiled contract") } - if err := bc.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, amt); err != nil { + if err := bc.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, sdk.NewCoins(amt)); err != nil { return errorsmod.Wrap(err, "fail to send mint coins to account") } return nil @@ -176,16 +194,22 @@ func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ( if amount.Sign() <= 0 { return nil, errors.New("invalid amount") } + from := sdk.AccAddress(sender.Bytes()) + to := sdk.AccAddress(recipient.Bytes()) + if err := bc.checkBlockedAddr(to); err != nil { + return nil, err + } + nativeAmt := sdk.NewCoin(types.DefaultEVMDenom, sdkmath.NewIntFromBigInt(amount)) + denom := EVMDenom(contract.CallerAddress) + amt := sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount)) err = bc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { - from := sdk.AccAddress(sender.Bytes()) - to := sdk.AccAddress(recipient.Bytes()) - nativeAmt := sdk.NewCoins(sdk.NewCoin(types.DefaultEVMDenom, sdkmath.NewIntFromBigInt(amount))) - if err := bc.bankKeeper.SendCoins(ctx, from, to, nativeAmt); err != nil { + if err := bc.bankKeeper.IsSendEnabledCoins(ctx, nativeAmt, amt); err != nil { + return err + } + if err := bc.bankKeeper.SendCoins(ctx, from, to, sdk.NewCoins(nativeAmt)); err != nil { return errorsmod.Wrap(err, "fail to send coins from sender to recipient") } - denom := EVMDenom(contract.CallerAddress) - amt := sdk.NewCoins(sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount))) - if err := bc.bankKeeper.SendCoins(ctx, from, to, amt); err != nil { + if err := bc.bankKeeper.SendCoins(ctx, from, to, sdk.NewCoins(amt)); err != nil { return errorsmod.Wrap(err, "fail to send coins in precompiled contract") } return nil diff --git a/x/evm/types/interfaces.go b/x/evm/types/interfaces.go index 89ba85afe7..8253dd22cd 100644 --- a/x/evm/types/interfaces.go +++ b/x/evm/types/interfaces.go @@ -49,6 +49,8 @@ type BankKeeper interface { SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error + BlockedAddr(addr sdk.AccAddress) bool } // StakingKeeper returns the historical headers kept in store. From bfb8e54469ef19269b46a495c8413be75755c490 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 20 Feb 2023 09:50:53 +0800 Subject: [PATCH 26/41] fix lint --- app/app.go | 1 - tests/integration_tests/test_precompiles.py | 1 - 2 files changed, 2 deletions(-) diff --git a/app/app.go b/app/app.go index 5dc1c21b7c..77728ee3df 100644 --- a/app/app.go +++ b/app/app.go @@ -130,7 +130,6 @@ import ( feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" // Force-load the tracer engines to trigger registration due to Go-Ethereum v1.10.15 changes - _ "github.com/ethereum/go-ethereum/eth/tracers/js" _ "github.com/ethereum/go-ethereum/eth/tracers/native" ) diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index dc4abb1451..5ee9f6abe8 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -77,7 +77,6 @@ def assert_crc20_balance(address, amt): # test transfer to blocked address recipient = module_address("evm") amt4 = 20 - recipient_balance = get_balance(cli, recipient, native_denom) with pytest.raises(web3.exceptions.ContractLogicError): tx = contract.functions.nativeTransfer(recipient, amt4).build_transaction( {"from": sender} From 18046802cd6836e5897d6532186c20790ded8f08 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 20 Feb 2023 13:06:11 +0800 Subject: [PATCH 27/41] fix commit --- x/evm/statedb/statedb.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index b533aced24..8c9d596194 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -46,8 +46,9 @@ var _ vm.StateDB = &StateDB{} // * Contracts // * Accounts type StateDB struct { - keeper Keeper - ctx sdk.Context + keeper Keeper + ctx sdk.Context + cacheCtx sdk.Context // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. @@ -72,10 +73,11 @@ type StateDB struct { // New creates a new state from a given trie, StateDB will branch out stores for the specified keys to support // precompiles. func New(ctx sdk.Context, keys map[string]*storetypes.KVStoreKey, keeper Keeper, txConfig TxConfig) *StateDB { - ctx = ctx.WithMultiStore(cachemulti.NewStore(ctx.MultiStore(), keys)) + cacheCtx := ctx.WithMultiStore(cachemulti.NewStore(ctx.MultiStore(), keys)) return &StateDB{ keeper: keeper, ctx: ctx, + cacheCtx: cacheCtx, stateObjects: make(map[common.Address]*stateObject), journal: newJournal(), accessList: newAccessList(), @@ -88,7 +90,7 @@ func New(ctx sdk.Context, keys map[string]*storetypes.KVStoreKey, keeper Keeper, // invariant: the multistore must be a `cachemulti.Store`, // prove: it's set in constructor and only modified in `restoreNativeState` which keeps the invariant. func (s *StateDB) CacheMultiStore() cachemulti.Store { - return s.ctx.MultiStore().(cachemulti.Store) + return s.cacheCtx.MultiStore().(cachemulti.Store) } // Keeper returns the underlying `Keeper` @@ -310,7 +312,7 @@ func (s *StateDB) setStateObject(object *stateObject) { } func (s *StateDB) restoreNativeState(ms sdk.MultiStore) { - s.ctx = s.ctx.WithMultiStore(ms) + s.cacheCtx = s.cacheCtx.WithMultiStore(ms) } // ExecuteNativeAction executes native action in isolate, @@ -318,7 +320,7 @@ func (s *StateDB) restoreNativeState(ms sdk.MultiStore) { // or the wrapping message call reverted. func (s *StateDB) ExecuteNativeAction(action func(ctx sdk.Context) error) error { snapshot := s.CacheMultiStore().Clone() - err := action(s.ctx) + err := action(s.cacheCtx) if err != nil { s.restoreNativeState(snapshot) return err From 778e165161eb97c8f1244e9c6e1afa277dccb636 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Fri, 17 Feb 2023 15:16:24 +0800 Subject: [PATCH 28/41] fix test --- app/ante/utils_test.go | 2 +- tests/importer/importer_test.go | 2 +- x/evm/handler_test.go | 2 +- x/evm/keeper/grpc_query_test.go | 2 +- x/evm/keeper/hooks_test.go | 2 +- x/evm/keeper/keeper_test.go | 2 +- x/evm/keeper/statedb_test.go | 2 +- x/evm/statedb/statedb_test.go | 38 ++++++++++++++++----------------- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 838ad63f7f..c76872569e 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -74,7 +74,7 @@ type AnteTestSuite struct { const TestGasLimit uint64 = 100000 func (suite *AnteTestSuite) StateDB() *statedb.StateDB { - return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) + return statedb.New(suite.ctx, nil, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) } func (suite *AnteTestSuite) SetupTest() { diff --git a/tests/importer/importer_test.go b/tests/importer/importer_test.go index a32a730206..2473c1b6dc 100644 --- a/tests/importer/importer_test.go +++ b/tests/importer/importer_test.go @@ -140,7 +140,7 @@ func (suite *ImporterTestSuite) TestImportBlocks() { }) ctx := suite.app.NewContext(false, tmheader) ctx = ctx.WithBlockHeight(tmheader.Height) - vmdb := statedb.New(ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) + vmdb := statedb.New(ctx, nil, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 { applyDAOHardFork(vmdb) diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index a554c6ee6a..a70fafd061 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -182,7 +182,7 @@ func (suite *EvmTestSuite) SignTx(tx *types.MsgEthereumTx) { } func (suite *EvmTestSuite) StateDB() *statedb.StateDB { - return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) + return statedb.New(suite.ctx, nil, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) } func TestEvmTestSuite(t *testing.T) { diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index a56b78ff48..a637b77dea 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -392,7 +392,7 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() { suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { suite.SetupTest() // reset - vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()), txHash, txIndex, logIndex)) + vmdb := statedb.New(suite.ctx, nil, suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()), txHash, txIndex, logIndex)) tc.malleate(vmdb) suite.Require().NoError(vmdb.Commit()) diff --git a/x/evm/keeper/hooks_test.go b/x/evm/keeper/hooks_test.go index b635cf4cd4..274a75f458 100644 --- a/x/evm/keeper/hooks_test.go +++ b/x/evm/keeper/hooks_test.go @@ -66,7 +66,7 @@ func (suite *KeeperTestSuite) TestEvmHooks() { k := suite.app.EvmKeeper ctx := suite.ctx txHash := common.BigToHash(big.NewInt(1)) - vmdb := statedb.New(ctx, k, statedb.NewTxConfig( + vmdb := statedb.New(ctx, nil, k, statedb.NewTxConfig( common.BytesToHash(ctx.HeaderHash().Bytes()), txHash, 0, diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 1b89527735..7ee89f2c10 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -253,7 +253,7 @@ func (suite *KeeperTestSuite) Commit() { } func (suite *KeeperTestSuite) StateDB() *statedb.StateDB { - return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) + return statedb.New(suite.ctx, nil, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) } // DeployTestContract deploy a test erc20 contract and returns the contract address diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index b12d0dd460..2d8fd05ece 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -690,7 +690,7 @@ func (suite *KeeperTestSuite) TestAddLog() { for _, tc := range testCases { suite.Run(tc.name, func() { suite.SetupTest() - vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig( + vmdb := statedb.New(suite.ctx, nil, suite.app.EvmKeeper, statedb.NewTxConfig( common.BytesToHash(suite.ctx.HeaderHash().Bytes()), tc.hash, 0, 0, diff --git a/x/evm/statedb/statedb_test.go b/x/evm/statedb/statedb_test.go index 3a491aa8cc..586d49315c 100644 --- a/x/evm/statedb/statedb_test.go +++ b/x/evm/statedb/statedb_test.go @@ -52,7 +52,7 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().Empty(acct.states) suite.Require().False(acct.account.IsContract()) - db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) + db = statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) suite.Require().Equal(true, db.Exist(address)) suite.Require().Equal(true, db.Empty(address)) suite.Require().Equal(big.NewInt(0), db.GetBalance(address)) @@ -74,7 +74,7 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().NoError(db.Commit()) // suicide - db = statedb.New(sdk.Context{}, db.Keeper(), emptyTxConfig) + db = statedb.New(sdk.Context{}, nil, db.Keeper(), emptyTxConfig) suite.Require().False(db.HasSuicided(address)) suite.Require().True(db.Suicide(address)) @@ -89,7 +89,7 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().NoError(db.Commit()) // not accessible from StateDB anymore - db = statedb.New(sdk.Context{}, db.Keeper(), emptyTxConfig) + db = statedb.New(sdk.Context{}, nil, db.Keeper(), emptyTxConfig) suite.Require().False(db.Exist(address)) // and cleared in keeper too @@ -101,7 +101,7 @@ func (suite *StateDBTestSuite) TestAccount() { for _, tc := range testCases { suite.Run(tc.name, func() { keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) tc.malleate(db) }) } @@ -109,7 +109,7 @@ func (suite *StateDBTestSuite) TestAccount() { func (suite *StateDBTestSuite) TestAccountOverride() { keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) // test balance carry over when overwritten amount := big.NewInt(1) @@ -140,7 +140,7 @@ func (suite *StateDBTestSuite) TestDBError() { }}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), emptyTxConfig) tc.malleate(db) suite.Require().Error(db.Commit()) } @@ -173,7 +173,7 @@ func (suite *StateDBTestSuite) TestBalance() { for _, tc := range testCases { suite.Run(tc.name, func() { keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) tc.malleate(db) // check dirty state @@ -225,7 +225,7 @@ func (suite *StateDBTestSuite) TestState() { for _, tc := range testCases { suite.Run(tc.name, func() { keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) tc.malleate(db) suite.Require().NoError(db.Commit()) @@ -233,7 +233,7 @@ func (suite *StateDBTestSuite) TestState() { suite.Require().Equal(tc.expStates, keeper.accounts[address].states) // check ForEachStorage - db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) + db = statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) collected := CollectContractStorage(db) if len(tc.expStates) > 0 { suite.Require().Equal(tc.expStates, collected) @@ -266,7 +266,7 @@ func (suite *StateDBTestSuite) TestCode() { for _, tc := range testCases { suite.Run(tc.name, func() { keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) tc.malleate(db) // check dirty state @@ -277,7 +277,7 @@ func (suite *StateDBTestSuite) TestCode() { suite.Require().NoError(db.Commit()) // check again - db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) + db = statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) suite.Require().Equal(tc.expCode, db.GetCode(address)) suite.Require().Equal(len(tc.expCode), db.GetCodeSize(address)) suite.Require().Equal(tc.expCodeHash, db.GetCodeHash(address)) @@ -335,7 +335,7 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { { // do some arbitrary changes to the storage - db := statedb.New(ctx, keeper, emptyTxConfig) + db := statedb.New(ctx, nil, keeper, emptyTxConfig) db.SetNonce(address, 1) db.AddBalance(address, big.NewInt(100)) db.SetCode(address, []byte("hello world")) @@ -347,7 +347,7 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { originalKeeper := keeper.Clone() // run test - db := statedb.New(ctx, keeper, emptyTxConfig) + db := statedb.New(ctx, nil, keeper, emptyTxConfig) rev := db.Snapshot() tc.malleate(db) db.RevertToSnapshot(rev) @@ -369,7 +369,7 @@ func (suite *StateDBTestSuite) TestNestedSnapshot() { value1 := common.BigToHash(big.NewInt(1)) value2 := common.BigToHash(big.NewInt(2)) - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), emptyTxConfig) rev1 := db.Snapshot() db.SetState(address, key, value1) @@ -386,7 +386,7 @@ func (suite *StateDBTestSuite) TestNestedSnapshot() { } func (suite *StateDBTestSuite) TestInvalidSnapshotId() { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), emptyTxConfig) suite.Require().Panics(func() { db.RevertToSnapshot(1) }) @@ -458,7 +458,7 @@ func (suite *StateDBTestSuite) TestAccessList() { } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), emptyTxConfig) tc.malleate(db) } } @@ -471,7 +471,7 @@ func (suite *StateDBTestSuite) TestLog() { txHash, 1, 1, ) - db := statedb.New(sdk.Context{}, NewMockKeeper(), txConfig) + db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), txConfig) data := []byte("hello world") db.AddLog(ðtypes.Log{ Address: address, @@ -523,7 +523,7 @@ func (suite *StateDBTestSuite) TestRefund() { }, 0, true}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), emptyTxConfig) if !tc.expPanic { tc.malleate(db) suite.Require().Equal(tc.expRefund, db.GetRefund()) @@ -542,7 +542,7 @@ func (suite *StateDBTestSuite) TestIterateStorage() { value2 := common.BigToHash(big.NewInt(4)) keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) db.SetState(address, key1, value1) db.SetState(address, key2, value2) From 2b4e50757e78c670134558ecf4c4bc427e7203f0 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Fri, 17 Feb 2023 15:46:45 +0800 Subject: [PATCH 29/41] fix build --- default.nix | 2 +- gomod2nix.toml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/default.nix b/default.nix index 9b6216094b..884593d61f 100644 --- a/default.nix +++ b/default.nix @@ -17,7 +17,7 @@ in buildGoApplication rec { inherit pname version tags ldflags; src = lib.sourceByRegex ./. [ - "^(x|app|cmd|client|server|crypto|rpc|types|encoding|ethereum|indexer|testutil|version|go.mod|go.sum|gomod2nix.toml)($|/.*)" + "^(x|app|cmd|client|server|store|crypto|rpc|types|encoding|ethereum|indexer|testutil|version|go.mod|go.sum|gomod2nix.toml)($|/.*)" "^tests(/.*[.]go)?$" ]; modules = ./gomod2nix.toml; diff --git a/gomod2nix.toml b/gomod2nix.toml index 65ab566060..7a1752e058 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -490,8 +490,8 @@ schema = 3 version = "v0.0.0-20220722155223-a9213eeb770e" hash = "sha256-kNgzydWRpjm0sZl4uXEs3LX5L0xjJtJRAFf/CTlYUN4=" [mod."golang.org/x/net"] - version = "v0.5.0" - hash = "sha256-HpbIAiLs7S1+tVsaSSdbCPw1IK43A0bFFuSzPSyjLbo=" + version = "v0.6.0" + hash = "sha256-e8F4kMogxT392mmimvcAmkUtD/5vcQRPWDwH238m8FU=" [mod."golang.org/x/oauth2"] version = "v0.0.0-20221014153046-6fdb5e3db783" hash = "sha256-IoygidVNqyAZmN+3macDeIefK8hhJToygpcqlwehdYQ=" @@ -499,14 +499,14 @@ schema = 3 version = "v0.1.0" hash = "sha256-Hygjq9euZ0qz6TvHYQwOZEjNiTbTh1nSLRAWZ6KFGR8=" [mod."golang.org/x/sys"] - version = "v0.4.0" - hash = "sha256-jchMzHCH5dg+IL/F+LqaX/fyAcB/nvHQpfBjqwaRJH0=" + version = "v0.5.0" + hash = "sha256-0LTr3KeJ1OMQAwYUQo1513dXJtQAJn5Dq8sFkc8ps1U=" [mod."golang.org/x/term"] - version = "v0.4.0" - hash = "sha256-wQKxHV10TU4vCU8Re2/hFmAbur/jRWEOB8QXBzgTFNY=" + version = "v0.5.0" + hash = "sha256-f3DiX7NkDsEZpPS+PbmnOH9F5WHFZ1sQrfFg/T2UPno=" [mod."golang.org/x/text"] - version = "v0.6.0" - hash = "sha256-+bpeRWR3relKACdal6NPj+eP5dnWCplTViArSN7/qA4=" + version = "v0.7.0" + hash = "sha256-ydgUqX+t5Qke16C6d3FP/06U/N1n+rUKpLRFj4KXjwk=" [mod."golang.org/x/xerrors"] version = "v0.0.0-20220907171357-04be3eba64a2" hash = "sha256-6+zueutgefIYmgXinOflz8qGDDDj0Zhv+2OkGhBTKno=" From ae3038584684bea18136b0b8d486822cf66eb160 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Sat, 18 Feb 2023 18:51:52 +0800 Subject: [PATCH 30/41] pass keys --- app/ante/utils_test.go | 2 +- app/app.go | 7 +++++++ tests/importer/importer_test.go | 2 +- x/evm/handler_test.go | 2 +- x/evm/keeper/grpc_query_test.go | 2 +- x/evm/keeper/hooks_test.go | 2 +- x/evm/keeper/keeper_test.go | 2 +- x/evm/keeper/statedb_test.go | 2 +- 8 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index c76872569e..24d8fff545 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -74,7 +74,7 @@ type AnteTestSuite struct { const TestGasLimit uint64 = 100000 func (suite *AnteTestSuite) StateDB() *statedb.StateDB { - return statedb.New(suite.ctx, nil, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) + return statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) } func (suite *AnteTestSuite) SetupTest() { diff --git a/app/app.go b/app/app.go index deb3903a7e..972e6579f7 100644 --- a/app/app.go +++ b/app/app.go @@ -757,6 +757,13 @@ func (app *EthermintApp) InterfaceRegistry() types.InterfaceRegistry { return app.interfaceRegistry } +// GetKeys returns the KVStoreKeys. +// +// NOTE: This is solely to be used for testing purposes. +func (app *EthermintApp) GetKeys() map[string]*storetypes.KVStoreKey { + return app.keys +} + // GetKey returns the KVStoreKey for the provided store key. // // NOTE: This is solely to be used for testing purposes. diff --git a/tests/importer/importer_test.go b/tests/importer/importer_test.go index 2473c1b6dc..24c69d83d6 100644 --- a/tests/importer/importer_test.go +++ b/tests/importer/importer_test.go @@ -140,7 +140,7 @@ func (suite *ImporterTestSuite) TestImportBlocks() { }) ctx := suite.app.NewContext(false, tmheader) ctx = ctx.WithBlockHeight(tmheader.Height) - vmdb := statedb.New(ctx, nil, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) + vmdb := statedb.New(ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 { applyDAOHardFork(vmdb) diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index a70fafd061..b14f9658c8 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -182,7 +182,7 @@ func (suite *EvmTestSuite) SignTx(tx *types.MsgEthereumTx) { } func (suite *EvmTestSuite) StateDB() *statedb.StateDB { - return statedb.New(suite.ctx, nil, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) + return statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) } func TestEvmTestSuite(t *testing.T) { diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index a637b77dea..2cb670170b 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -392,7 +392,7 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() { suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { suite.SetupTest() // reset - vmdb := statedb.New(suite.ctx, nil, suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()), txHash, txIndex, logIndex)) + vmdb := statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()), txHash, txIndex, logIndex)) tc.malleate(vmdb) suite.Require().NoError(vmdb.Commit()) diff --git a/x/evm/keeper/hooks_test.go b/x/evm/keeper/hooks_test.go index 274a75f458..4c01bf2d71 100644 --- a/x/evm/keeper/hooks_test.go +++ b/x/evm/keeper/hooks_test.go @@ -66,7 +66,7 @@ func (suite *KeeperTestSuite) TestEvmHooks() { k := suite.app.EvmKeeper ctx := suite.ctx txHash := common.BigToHash(big.NewInt(1)) - vmdb := statedb.New(ctx, nil, k, statedb.NewTxConfig( + vmdb := statedb.New(ctx, suite.app.GetKeys(), k, statedb.NewTxConfig( common.BytesToHash(ctx.HeaderHash().Bytes()), txHash, 0, diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 7ee89f2c10..0acd382a8e 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -253,7 +253,7 @@ func (suite *KeeperTestSuite) Commit() { } func (suite *KeeperTestSuite) StateDB() *statedb.StateDB { - return statedb.New(suite.ctx, nil, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) + return statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) } // DeployTestContract deploy a test erc20 contract and returns the contract address diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index 2d8fd05ece..1bf3c45f46 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -690,7 +690,7 @@ func (suite *KeeperTestSuite) TestAddLog() { for _, tc := range testCases { suite.Run(tc.name, func() { suite.SetupTest() - vmdb := statedb.New(suite.ctx, nil, suite.app.EvmKeeper, statedb.NewTxConfig( + vmdb := statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewTxConfig( common.BytesToHash(suite.ctx.HeaderHash().Bytes()), tc.hash, 0, 0, From 5a1b0e0caaaf3686cecd9f420cc40bc8fdcbb955 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Sun, 19 Feb 2023 09:34:11 +0800 Subject: [PATCH 31/41] add keys for CanTransferDecorator --- app/ante/eth.go | 10 +++++----- app/ante/eth_test.go | 2 +- app/ante/handler_options.go | 4 +++- app/ante/utils_test.go | 1 + app/app.go | 1 + 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/ante/eth.go b/app/ante/eth.go index 87322f980a..15c25b09e4 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -22,6 +22,7 @@ import ( errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" + storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" @@ -246,13 +247,12 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula // context rules. type CanTransferDecorator struct { evmKeeper EVMKeeper + keys map[string]*storetypes.KVStoreKey } // NewCanTransferDecorator creates a new CanTransferDecorator instance. -func NewCanTransferDecorator(evmKeeper EVMKeeper) CanTransferDecorator { - return CanTransferDecorator{ - evmKeeper: evmKeeper, - } +func NewCanTransferDecorator(evmKeeper EVMKeeper, keys map[string]*storetypes.KVStoreKey) CanTransferDecorator { + return CanTransferDecorator{evmKeeper, keys} } // AnteHandle creates an EVM from the message and calls the BlockContext CanTransfer function to @@ -302,7 +302,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate BaseFee: baseFee, } - stateDB := statedb.New(ctx, nil, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) + stateDB := statedb.New(ctx, ctd.keys, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB) // check that caller has enough balance to cover asset transfer for **topmost** call diff --git a/app/ante/eth_test.go b/app/ante/eth_test.go index 0fb9ea8b20..c63e78b395 100644 --- a/app/ante/eth_test.go +++ b/app/ante/eth_test.go @@ -318,7 +318,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() { } func (suite AnteTestSuite) TestCanTransferDecorator() { - dec := ante.NewCanTransferDecorator(suite.app.EvmKeeper) + dec := ante.NewCanTransferDecorator(suite.app.EvmKeeper, suite.app.GetKeys()) addr, privKey := tests.NewAddrKey() diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index b9df6f8289..02a120dec4 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -27,6 +27,7 @@ import ( ibcante "github.com/cosmos/ibc-go/v6/modules/core/ante" ibckeeper "github.com/cosmos/ibc-go/v6/modules/core/keeper" + storetypes "github.com/cosmos/cosmos-sdk/store/types" evmtypes "github.com/evmos/ethermint/x/evm/types" ) @@ -44,6 +45,7 @@ type HandlerOptions struct { MaxTxGasWanted uint64 ExtensionOptionChecker ante.ExtensionOptionChecker TxFeeChecker ante.TxFeeChecker + Keys map[string]*storetypes.KVStoreKey } func (options HandlerOptions) validate() error { @@ -73,7 +75,7 @@ func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler { NewEthValidateBasicDecorator(options.EvmKeeper), NewEthSigVerificationDecorator(options.EvmKeeper), NewEthAccountVerificationDecorator(options.AccountKeeper, options.EvmKeeper), - NewCanTransferDecorator(options.EvmKeeper), + NewCanTransferDecorator(options.EvmKeeper, options.Keys), NewEthGasConsumeDecorator(options.EvmKeeper, options.MaxTxGasWanted), NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator. NewGasWantedDecorator(options.EvmKeeper, options.FeeMarketKeeper), diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 24d8fff545..33f647219c 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -133,6 +133,7 @@ func (suite *AnteTestSuite) SetupTest() { FeeMarketKeeper: suite.app.FeeMarketKeeper, SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + Keys: suite.app.GetKeys(), }) suite.Require().NoError(err) diff --git a/app/app.go b/app/app.go index 972e6579f7..e36a4809b4 100644 --- a/app/app.go +++ b/app/app.go @@ -668,6 +668,7 @@ func (app *EthermintApp) setAnteHandler(txConfig client.TxConfig, maxGasWanted u MaxTxGasWanted: maxGasWanted, ExtensionOptionChecker: ethermint.HasDynamicFeeExtensionOption, TxFeeChecker: ante.NewDynamicFeeChecker(app.EvmKeeper), + Keys: app.keys, }) if err != nil { panic(err) From 54091f4b424f27e685d1564b45eb488e5536e91b Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 20 Feb 2023 13:33:38 +0800 Subject: [PATCH 32/41] fix nix --- gomod2nix.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 01a635e6c6..b82c72d9a3 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -97,7 +97,7 @@ schema = 3 hash = "sha256-oATkuj+fM5eBn+ywO+w/tL0AFSIEkx0J3Yz+VhVe0QA=" [mod."github.com/cosmos/cosmos-sdk"] version = "v0.46.8" - hash = "sha256-jjGeuviv1YvaiUQD2Ojpyivf6niEdjdKdRNhkvHAA2w=" + hash = "sha256-jETbixsZq5XJ1pl1XaVJvD5jkcygBrEcHRK/L0+XI68=" [mod."github.com/cosmos/go-bip39"] version = "v1.0.0" hash = "sha256-Qm2aC2vaS8tjtMUbHmlBSagOSqbduEEDwc51qvQaBmA=" From 692e69f366b33c01213f66667a99254b9a961bf0 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 21 Feb 2023 09:34:47 +0800 Subject: [PATCH 33/41] fix crc20 <-> native token --- .../hardhat/contracts/TestBank.sol | 17 +++- tests/integration_tests/test_precompiles.py | 92 ++++++++++--------- x/evm/keeper/precompiles/bank.go | 51 +++++----- 3 files changed, 82 insertions(+), 78 deletions(-) diff --git a/tests/integration_tests/hardhat/contracts/TestBank.sol b/tests/integration_tests/hardhat/contracts/TestBank.sol index e7a6a2cc45..1f5f94bfc1 100644 --- a/tests/integration_tests/hardhat/contracts/TestBank.sol +++ b/tests/integration_tests/hardhat/contracts/TestBank.sol @@ -1,19 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity >0.6.6; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -contract TestBank { +contract TestBank is ERC20 { address constant bankContract = 0x0000000000000000000000000000000000000064; - function nativeMint(uint256 amount) public { + constructor() public ERC20("Bitcoin MAX", "MAX") { + _mint(msg.sender, 100000000000000000000000000); + } + function moveToNative(uint256 amount) public { + _burn(msg.sender, amount); (bool result, ) = bankContract.call(abi.encodeWithSignature( "mint(address,uint256)", msg.sender, amount )); require(result, "native call"); } - function nativeBurn(uint256 amount) public { + function moveFromNative(uint256 amount) public { (bool result, ) = bankContract.call(abi.encodeWithSignature( "burn(address,uint256)", msg.sender, amount )); require(result, "native call"); + _mint(msg.sender, amount); } function nativeBalanceOf(address addr) public returns (uint256) { (bool result, bytes memory data) = bankContract.call(abi.encodeWithSignature( @@ -22,11 +28,12 @@ contract TestBank { require(result, "native call"); return abi.decode(data, (uint256)); } - function nativeMintRevert(uint256 amount) public { - nativeMint(amount); + function moveToNativeRevert(uint256 amount) public { + moveToNative(amount); revert("test"); } function nativeTransfer(address recipient, uint256 amount) public { + _transfer(msg.sender, recipient, amount); (bool result, ) = bankContract.call(abi.encodeWithSignature( "transfer(address,address,uint256)", msg.sender, recipient, amount )); diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index 5ee9f6abe8..5abd657abe 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -19,82 +19,84 @@ def get_balance(cli, addr, denom): def test_call(ethermint): w3 = ethermint.w3 cli = ethermint.cosmos_cli() - native_denom = "aphoton" - sender = ADDRS["signer1"] + addr = ADDRS["signer1"] keys = KEYS["signer1"] - amt1 = 100 - contract, _ = deploy_contract(w3, CONTRACTS["TestBank"]) + contract, _ = deploy_contract(w3, CONTRACTS["TestBank"], (), keys) denom = "evm/" + contract.address - def assert_sender_balance(tx, expect_status, amt): - balance = get_balance(cli, sender, native_denom) + def assert_balance(tx, expect_status, amt): + balance = get_balance(cli, addr, denom) + assert balance == contract.caller.nativeBalanceOf(addr) + crc20_balance = contract.caller.balanceOf(addr) receipt = send_transaction(w3, tx, keys) assert receipt.status == expect_status - fee = receipt["cumulativeGasUsed"] * receipt["effectiveGasPrice"] - current = get_balance(cli, sender, native_denom) - assert balance == current + fee + amt - - def assert_crc20_balance(address, amt): - # query balance through contract - assert contract.caller.nativeBalanceOf(address) == amt - # query balance through cosmos rpc - assert get_balance(cli, address, denom) == amt + balance += amt + assert balance == get_balance(cli, addr, denom) + assert balance == contract.caller.nativeBalanceOf(addr) + assert crc20_balance - amt == contract.caller.balanceOf(addr) # test mint - tx = contract.functions.nativeMint(amt1).build_transaction({"from": sender}) - assert_sender_balance(tx, 1, amt1) - balance = amt1 - assert_crc20_balance(sender, balance) + amt1 = 100 + tx = contract.functions.moveToNative(amt1).build_transaction({"from": addr}) + assert_balance(tx, 1, amt1) # test exception revert - tx = contract.functions.nativeMintRevert(amt1).build_transaction( - {"from": sender, "gas": 210000} + tx = contract.functions.moveToNativeRevert(amt1).build_transaction( + {"from": addr, "gas": 210000} ) - # check balance don't change - assert_sender_balance(tx, 0, 0) - assert_crc20_balance(sender, balance) + assert_balance(tx, 0, 0) # test burn amt2 = 50 - tx = contract.functions.nativeBurn(amt2).build_transaction({"from": sender}) - assert_sender_balance(tx, 1, -amt2) - balance -= amt2 - assert_crc20_balance(sender, balance) + tx = contract.functions.moveFromNative(amt2).build_transaction({"from": addr}) + assert_balance(tx, 1, -amt2) # test transfer - recipient = ADDRS["signer2"] amt3 = 10 - recipient_balance = get_balance(cli, recipient, native_denom) - tx = contract.functions.nativeTransfer(recipient, amt3).build_transaction( - {"from": sender} + addr2 = ADDRS["signer2"] + tx = contract.functions.nativeTransfer(addr2, amt3).build_transaction( + {"from": addr} ) - assert_sender_balance(tx, 1, amt3) + balance = get_balance(cli, addr, denom) + assert balance == contract.caller.nativeBalanceOf(addr) + crc20_balance = contract.caller.balanceOf(addr) + + balance2 = get_balance(cli, addr2, denom) + assert balance2 == contract.caller.nativeBalanceOf(addr2) + crc20_balance2 = contract.caller.balanceOf(addr2) + + receipt = send_transaction(w3, tx, keys) + assert receipt.status == 1 + balance -= amt3 - assert_crc20_balance(sender, balance) - assert get_balance(cli, recipient, native_denom) == recipient_balance + amt3 - assert_crc20_balance(recipient, amt3) + assert balance == get_balance(cli, addr, denom) + assert balance == contract.caller.nativeBalanceOf(addr) + assert crc20_balance - amt3 == contract.caller.balanceOf(addr) + + balance2 += amt3 + assert balance2 == get_balance(cli, addr2, denom) + assert balance2 == contract.caller.nativeBalanceOf(addr2) + assert crc20_balance2 + amt3 == contract.caller.balanceOf(addr2) # test transfer to blocked address recipient = module_address("evm") amt4 = 20 with pytest.raises(web3.exceptions.ContractLogicError): tx = contract.functions.nativeTransfer(recipient, amt4).build_transaction( - {"from": sender} + {"from": addr} ) - send_transaction(w3, tx, keys) - # check balance don't change - assert_crc20_balance(sender, balance) def test_delegate(ethermint): w3 = ethermint.w3 - addr = ADDRS["validator"] + addr = ADDRS["signer1"] + keys = KEYS["signer1"] amount = 100 - _, res = deploy_contract(w3, CONTRACTS["TestBank"]) + _, res = deploy_contract(w3, CONTRACTS["TestBank"], (), keys) bank = res["contractAddress"] - contract, _ = deploy_contract(w3, CONTRACTS["TestBankDelegate"]) + contract, _ = deploy_contract(w3, CONTRACTS["TestBankDelegate"], (), keys) data = {"from": addr} - tx = contract.functions.nativeMint(bank, amount).build_transaction(data) + tx = contract.functions.moveToNative(bank, amount).build_transaction(data) receipt = send_transaction(w3, tx) assert receipt.status == 1, "expect success" @@ -106,7 +108,7 @@ def test_delegate(ethermint): assert get_balance(cli, addr, denom) == amount # test exception revert - tx = contract.functions.nativeMintRevert(bank, amount).build_transaction( + tx = contract.functions.moveToNativeRevert(bank, amount).build_transaction( {"from": addr, "gas": 210000} ) receipt = send_transaction(w3, tx) diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index ac32705d38..6f6e781e11 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -120,19 +120,15 @@ func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ( methodID := contract.Input[:4] switch string(methodID) { case string(MintMethod.ID), string(BurnMethod.ID): + if readonly { + return nil, errors.New("the method is not readonly") + } + mint := string(methodID) == string(MintMethod.ID) var method abi.Method - var mintDenom, burnDenom string - if string(methodID) == string(MintMethod.ID) { + if mint { method = MintMethod - mintDenom = EVMDenom(contract.CallerAddress) - burnDenom = types.DefaultEVMDenom } else { method = BurnMethod - burnDenom = EVMDenom(contract.CallerAddress) - mintDenom = types.DefaultEVMDenom - } - if readonly { - return nil, errors.New("the method is not readonly") } args, err := method.Inputs.Unpack(contract.Input[4:]) if err != nil { @@ -147,23 +143,26 @@ func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ( if err := bc.checkBlockedAddr(addr); err != nil { return nil, err } - nativeAmt := sdk.NewCoin(burnDenom, sdkmath.NewIntFromBigInt(amount)) - amt := sdk.NewCoin(mintDenom, sdkmath.NewIntFromBigInt(amount)) + denom := EVMDenom(contract.CallerAddress) + amt := sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount)) err = bc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { - if err := bc.bankKeeper.IsSendEnabledCoins(ctx, nativeAmt, amt); err != nil { + if err := bc.bankKeeper.IsSendEnabledCoins(ctx, amt); err != nil { return err } - if err := bc.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, sdk.NewCoins(nativeAmt)); err != nil { - return errorsmod.Wrap(err, "fail to send burn coins to module") - } - if err := bc.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(nativeAmt)); err != nil { - return errorsmod.Wrap(err, "fail to burn coins in precompiled contract") - } - if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amt)); err != nil { - return errorsmod.Wrap(err, "fail to mint coins in precompiled contract") - } - if err := bc.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, sdk.NewCoins(amt)); err != nil { - return errorsmod.Wrap(err, "fail to send mint coins to account") + if mint { + if err := bc.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amt)); err != nil { + return errorsmod.Wrap(err, "fail to mint coins in precompiled contract") + } + if err := bc.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, sdk.NewCoins(amt)); err != nil { + return errorsmod.Wrap(err, "fail to send mint coins to account") + } + } else { + if err := bc.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, sdk.NewCoins(amt)); err != nil { + return errorsmod.Wrap(err, "fail to send burn coins to module") + } + if err := bc.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(amt)); err != nil { + return errorsmod.Wrap(err, "fail to burn coins in precompiled contract") + } } return nil }) @@ -199,16 +198,12 @@ func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ( if err := bc.checkBlockedAddr(to); err != nil { return nil, err } - nativeAmt := sdk.NewCoin(types.DefaultEVMDenom, sdkmath.NewIntFromBigInt(amount)) denom := EVMDenom(contract.CallerAddress) amt := sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount)) err = bc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { - if err := bc.bankKeeper.IsSendEnabledCoins(ctx, nativeAmt, amt); err != nil { + if err := bc.bankKeeper.IsSendEnabledCoins(ctx, amt); err != nil { return err } - if err := bc.bankKeeper.SendCoins(ctx, from, to, sdk.NewCoins(nativeAmt)); err != nil { - return errorsmod.Wrap(err, "fail to send coins from sender to recipient") - } if err := bc.bankKeeper.SendCoins(ctx, from, to, sdk.NewCoins(amt)); err != nil { return errorsmod.Wrap(err, "fail to send coins in precompiled contract") } From 6f5ac419dee7222f5da0e407b6108cc25840d8bc Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 21 Feb 2023 09:34:54 +0800 Subject: [PATCH 34/41] test delegate & static call --- .../hardhat/contracts/TestBank.sol | 76 ++++++++++++++++--- .../hardhat/contracts/TestBankCaller.sol | 2 +- .../hardhat/contracts/TestBankDelegate.sol | 22 ------ tests/integration_tests/test_precompiles.py | 73 ++++++++++-------- tests/integration_tests/utils.py | 1 - 5 files changed, 107 insertions(+), 67 deletions(-) delete mode 100644 tests/integration_tests/hardhat/contracts/TestBankDelegate.sol diff --git a/tests/integration_tests/hardhat/contracts/TestBank.sol b/tests/integration_tests/hardhat/contracts/TestBank.sol index 1f5f94bfc1..6435dbac16 100644 --- a/tests/integration_tests/hardhat/contracts/TestBank.sol +++ b/tests/integration_tests/hardhat/contracts/TestBank.sol @@ -7,24 +7,57 @@ contract TestBank is ERC20 { constructor() public ERC20("Bitcoin MAX", "MAX") { _mint(msg.sender, 100000000000000000000000000); } + function encodeMint(uint256 amount) internal view returns (bytes memory) { + return abi.encodeWithSignature("mint(address,uint256)", msg.sender, amount); + } function moveToNative(uint256 amount) public { _burn(msg.sender, amount); - (bool result, ) = bankContract.call(abi.encodeWithSignature( - "mint(address,uint256)", msg.sender, amount - )); + (bool result, ) = bankContract.call(encodeMint(amount)); + require(result, "native call"); + } + function moveToNativeDelegate(uint256 amount) public { + _burn(msg.sender, amount); + (bool result, ) = bankContract.delegatecall(encodeMint(amount)); require(result, "native call"); } + function moveToNativeStatic(uint256 amount) public { + _burn(msg.sender, amount); + (bool result, ) = bankContract.staticcall(encodeMint(amount)); + require(result, "native call"); + } + function encodeBurn(uint256 amount) internal view returns (bytes memory) { + return abi.encodeWithSignature("burn(address,uint256)", msg.sender, amount); + } function moveFromNative(uint256 amount) public { - (bool result, ) = bankContract.call(abi.encodeWithSignature( - "burn(address,uint256)", msg.sender, amount - )); + (bool result, ) = bankContract.call(encodeBurn(amount)); require(result, "native call"); _mint(msg.sender, amount); } + function moveFromNativeDelegate(uint256 amount) public { + (bool result, ) = bankContract.delegatecall(encodeBurn(amount)); + require(result, "native call"); + _mint(msg.sender, amount); + } + function moveFromNativeStatic(uint256 amount) public { + (bool result, ) = bankContract.staticcall(encodeBurn(amount)); + require(result, "native call"); + _mint(msg.sender, amount); + } + function encodeBalanceOf(address addr) internal view returns (bytes memory) { + return abi.encodeWithSignature("balanceOf(address,address)", address(this), addr); + } function nativeBalanceOf(address addr) public returns (uint256) { - (bool result, bytes memory data) = bankContract.call(abi.encodeWithSignature( - "balanceOf(address,address)", address(this), addr - )); + (bool result, bytes memory data) = bankContract.call(encodeBalanceOf(addr)); + require(result, "native call"); + return abi.decode(data, (uint256)); + } + function nativeBalanceOfDelegate(address addr) public returns (uint256) { + (bool result, bytes memory data) = bankContract.delegatecall(encodeBalanceOf(addr)); + require(result, "native call"); + return abi.decode(data, (uint256)); + } + function nativeBalanceOfStatic(address addr) public returns (uint256) { + (bool result, bytes memory data) = bankContract.staticcall(encodeBalanceOf(addr)); require(result, "native call"); return abi.decode(data, (uint256)); } @@ -32,11 +65,30 @@ contract TestBank is ERC20 { moveToNative(amount); revert("test"); } + function moveToNativeRevertDelegate(uint256 amount) public { + moveToNativeDelegate(amount); + revert("test"); + } + function moveToNativeRevertStatic(uint256 amount) public { + moveToNativeStatic(amount); + revert("test"); + } + function encodeTransfer(address recipient, uint256 amount) internal view returns (bytes memory) { + return abi.encodeWithSignature("transfer(address,address,uint256)", msg.sender, recipient, amount); + } function nativeTransfer(address recipient, uint256 amount) public { _transfer(msg.sender, recipient, amount); - (bool result, ) = bankContract.call(abi.encodeWithSignature( - "transfer(address,address,uint256)", msg.sender, recipient, amount - )); + (bool result, ) = bankContract.call(encodeTransfer(recipient, amount)); + require(result, "native transfer"); + } + function nativeTransferDelegate(address recipient, uint256 amount) public { + _transfer(msg.sender, recipient, amount); + (bool result, ) = bankContract.delegatecall(encodeTransfer(recipient, amount)); + require(result, "native transfer"); + } + function nativeTransferStatic(address recipient, uint256 amount) public { + _transfer(msg.sender, recipient, amount); + (bool result, ) = bankContract.staticcall(encodeTransfer(recipient, amount)); require(result, "native transfer"); } } \ No newline at end of file diff --git a/tests/integration_tests/hardhat/contracts/TestBankCaller.sol b/tests/integration_tests/hardhat/contracts/TestBankCaller.sol index ca1d9b3e2e..669a737dd6 100644 --- a/tests/integration_tests/hardhat/contracts/TestBankCaller.sol +++ b/tests/integration_tests/hardhat/contracts/TestBankCaller.sol @@ -6,7 +6,7 @@ contract TestBankCaller { function mint(address callee, uint amount) public { (bool success, bytes memory data) = callee.call(abi.encodeWithSignature( - "nativeMintRevert(uint256)", amount + "moveToNativeRevert(uint256)", amount )); if (!success) { // ignore the error and move on diff --git a/tests/integration_tests/hardhat/contracts/TestBankDelegate.sol b/tests/integration_tests/hardhat/contracts/TestBankDelegate.sol deleted file mode 100644 index cced84f3ab..0000000000 --- a/tests/integration_tests/hardhat/contracts/TestBankDelegate.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >0.6.6; - -contract TestBankDelegate { - function nativeMint(address _contract, uint256 amount) public { - (bool result, bytes memory _data) = _contract.delegatecall(abi.encodeWithSignature( - "nativeMint(uint256)", amount - )); - require(result, "native call"); - } - function nativeBalanceOf(address _contract, address addr) public returns (uint256) { - (bool result, bytes memory data) = _contract.delegatecall(abi.encodeWithSignature( - "nativeBalanceOf(address)", addr - )); - require(result, "native call"); - return abi.decode(data, (uint256)); - } - function nativeMintRevert(address _contract, uint256 amount) public { - nativeMint(_contract, amount); - revert("test"); - } -} diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index 5abd657abe..63fc1014b4 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -37,7 +37,8 @@ def assert_balance(tx, expect_status, amt): # test mint amt1 = 100 - tx = contract.functions.moveToNative(amt1).build_transaction({"from": addr}) + data = {"from": addr} + tx = contract.functions.moveToNative(amt1).build_transaction(data) assert_balance(tx, 1, amt1) # test exception revert @@ -48,15 +49,13 @@ def assert_balance(tx, expect_status, amt): # test burn amt2 = 50 - tx = contract.functions.moveFromNative(amt2).build_transaction({"from": addr}) + tx = contract.functions.moveFromNative(amt2).build_transaction(data) assert_balance(tx, 1, -amt2) # test transfer amt3 = 10 addr2 = ADDRS["signer2"] - tx = contract.functions.nativeTransfer(addr2, amt3).build_transaction( - {"from": addr} - ) + tx = contract.functions.nativeTransfer(addr2, amt3).build_transaction(data) balance = get_balance(cli, addr, denom) assert balance == contract.caller.nativeBalanceOf(addr) crc20_balance = contract.caller.balanceOf(addr) @@ -82,43 +81,55 @@ def assert_balance(tx, expect_status, amt): recipient = module_address("evm") amt4 = 20 with pytest.raises(web3.exceptions.ContractLogicError): - tx = contract.functions.nativeTransfer(recipient, amt4).build_transaction( - {"from": addr} - ) + tx = contract.functions.nativeTransfer(recipient, amt4).build_transaction(data) -def test_delegate(ethermint): +@pytest.mark.parametrize("suffix", ["Delegate", "Static"]) +def test_readonly_call(ethermint, suffix): w3 = ethermint.w3 + cli = ethermint.cosmos_cli() addr = ADDRS["signer1"] keys = KEYS["signer1"] - amount = 100 - _, res = deploy_contract(w3, CONTRACTS["TestBank"], (), keys) - bank = res["contractAddress"] - contract, _ = deploy_contract(w3, CONTRACTS["TestBankDelegate"], (), keys) - data = {"from": addr} - tx = contract.functions.moveToNative(bank, amount).build_transaction(data) - receipt = send_transaction(w3, tx) - assert receipt.status == 1, "expect success" - - # query balance through contract - assert contract.caller.nativeBalanceOf(bank, addr) == amount - # query balance through cosmos rpc - cli = ethermint.cosmos_cli() + contract, _ = deploy_contract(w3, CONTRACTS["TestBank"], (), keys) denom = "evm/" + contract.address - assert get_balance(cli, addr, denom) == amount + native_balance_of = getattr(contract.caller, "nativeBalanceOf" + suffix) + + def get_balances(): + return [ + contract.caller.balanceOf(addr), + native_balance_of(addr), + get_balance(cli, addr, denom), + ] + + balances = get_balances() + # test mint + amt1 = 100 + data = {"from": addr} + with pytest.raises(web3.exceptions.ContractLogicError): + tx = contract.functions["moveToNative" + suffix](amt1).build_transaction(data) # test exception revert - tx = contract.functions.moveToNativeRevert(bank, amount).build_transaction( + tx = contract.functions["moveToNativeRevert" + suffix](amt1).build_transaction( {"from": addr, "gas": 210000} ) - receipt = send_transaction(w3, tx) - assert receipt.status == 0, "expect failure" + receipt = send_transaction(w3, tx, keys) + print("receipt", receipt) + assert receipt.status == 0 - # check balance don't change - assert contract.caller.nativeBalanceOf(bank, addr) == amount - # query balance through cosmos rpc - cli = ethermint.cosmos_cli() - assert get_balance(cli, addr, denom) == amount + # test burn + amt2 = 50 + with pytest.raises(web3.exceptions.ContractLogicError): + contract.functions["moveFromNative" + suffix](amt2).build_transaction(data) + + # test transfer + amt3 = 10 + addr2 = ADDRS["signer2"] + native_transfer = contract.functions["nativeTransfer" + suffix] + with pytest.raises(web3.exceptions.ContractLogicError): + native_transfer(addr2, amt3).build_transaction(data) + + # balance no change + assert balances == get_balances() def test_nested(ethermint): diff --git a/tests/integration_tests/utils.py b/tests/integration_tests/utils.py index 2063c6ecbd..4d85d0fcc8 100644 --- a/tests/integration_tests/utils.py +++ b/tests/integration_tests/utils.py @@ -35,7 +35,6 @@ "Mars": "Mars.sol", "StateContract": "StateContract.sol", "TestBank": "TestBank.sol", - "TestBankDelegate": "TestBankDelegate.sol", "TestBankCaller": "TestBankCaller.sol", } From c0804be8df1413a5b33d5ec08889d9b0557d4a41 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 21 Feb 2023 12:47:52 +0800 Subject: [PATCH 35/41] fix lint --- tests/integration_tests/test_precompiles.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index 63fc1014b4..9f2717f950 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -81,7 +81,7 @@ def assert_balance(tx, expect_status, amt): recipient = module_address("evm") amt4 = 20 with pytest.raises(web3.exceptions.ContractLogicError): - tx = contract.functions.nativeTransfer(recipient, amt4).build_transaction(data) + contract.functions.nativeTransfer(recipient, amt4).build_transaction(data) @pytest.mark.parametrize("suffix", ["Delegate", "Static"]) @@ -106,14 +106,13 @@ def get_balances(): amt1 = 100 data = {"from": addr} with pytest.raises(web3.exceptions.ContractLogicError): - tx = contract.functions["moveToNative" + suffix](amt1).build_transaction(data) + contract.functions["moveToNative" + suffix](amt1).build_transaction(data) # test exception revert tx = contract.functions["moveToNativeRevert" + suffix](amt1).build_transaction( {"from": addr, "gas": 210000} ) receipt = send_transaction(w3, tx, keys) - print("receipt", receipt) assert receipt.status == 0 # test burn From d680d7626c10d56922404447080a1ade42311af7 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Thu, 13 Apr 2023 10:47:46 +0800 Subject: [PATCH 36/41] rm AppendJournalEntry --- x/evm/statedb/interfaces.go | 6 +----- x/evm/statedb/statedb.go | 6 ------ 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/x/evm/statedb/interfaces.go b/x/evm/statedb/interfaces.go index e4e83e09c3..0b1dcc7905 100644 --- a/x/evm/statedb/interfaces.go +++ b/x/evm/statedb/interfaces.go @@ -22,13 +22,9 @@ import ( ) // ExtStateDB defines an extension to the interface provided by the go-ethereum -// codebase to support additional state transition functionalities. In particular -// it supports appending a new entry to the state journal through -// AppendJournalEntry so that the state can be reverted after running -// stateful precompiled contracts. +// codebase to support additional state transition functionalities. type ExtStateDB interface { vm.StateDB - AppendJournalEntry(JournalEntry) } // Keeper provide underlying storage of StateDB diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 5d0d112c13..8c9d596194 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -98,12 +98,6 @@ func (s *StateDB) Keeper() Keeper { return s.keeper } -// AppendJournalEntry allow external module to append journal entry, -// to support snapshot revert for external states. -func (s *StateDB) AppendJournalEntry(entry JournalEntry) { - s.journal.append(entry) -} - // AddLog adds a log, called by evm. func (s *StateDB) AddLog(log *ethtypes.Log) { s.journal.append(addLogChange{}) From 7f6a1c26bcc3cace279d6d693523aaf6c30f628b Mon Sep 17 00:00:00 2001 From: mmsqe Date: Thu, 13 Apr 2023 11:08:05 +0800 Subject: [PATCH 37/41] fix resolve --- gomod2nix.toml | 2 +- x/evm/keeper/grpc_query_test.go | 18 ++++++------------ x/evm/keeper/statedb_test.go | 6 ++---- x/evm/keeper/utils_test.go | 4 ++-- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index e90187305e..4986107bea 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -550,7 +550,7 @@ schema = 3 hash = "sha256-2HzpK4s9zAGUv/26wChxbfkX3t4WB1bLM96O5gkQmro=" [mod."google.golang.org/protobuf"] version = "v1.29.1" - hash = "sha256-ZJVKkvwzFisv8IAKAmKNKwQSR+ni5pUNYPkloMjqsKA=" + hash = "sha256-ilSVvttGSP2xpqpoyQ0/Iuyx1WMiwe6GASKTfoeaqxw=" [mod."gopkg.in/ini.v1"] version = "v1.67.0" hash = "sha256-V10ahGNGT+NLRdKUyRg1dos5RxLBXBk1xutcnquc/+4=" diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index f0adc34845..2cb670170b 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -272,8 +272,7 @@ func (suite *KeeperTestSuite) TestQueryStorage() { vmdb := suite.StateDB() tc.malleate(vmdb) - _, err := vmdb.Commit() - suite.Require().NoError(err) + suite.Require().NoError(vmdb.Commit()) ctx := sdk.WrapSDKContext(suite.ctx) res, err := suite.queryClient.Storage(ctx, req) @@ -332,8 +331,7 @@ func (suite *KeeperTestSuite) TestQueryCode() { vmdb := suite.StateDB() tc.malleate(vmdb) - _, err := vmdb.Commit() - suite.Require().NoError(err) + suite.Require().NoError(vmdb.Commit()) ctx := sdk.WrapSDKContext(suite.ctx) res, err := suite.queryClient.Code(ctx, req) @@ -396,8 +394,7 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() { vmdb := statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()), txHash, txIndex, logIndex)) tc.malleate(vmdb) - _, err := vmdb.Commit() - suite.Require().NoError(err) + suite.Require().NoError(vmdb.Commit()) logs := vmdb.Logs() suite.Require().Equal(expLogs, types.NewLogsFromEth(logs)) @@ -840,8 +837,7 @@ func (suite *KeeperTestSuite) TestTraceTx() { // increase nonce to avoid address collision vmdb := suite.StateDB() vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1) - _, err := vmdb.Commit() - suite.Require().NoError(err) + suite.Require().NoError(vmdb.Commit()) contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) suite.Commit() @@ -900,8 +896,7 @@ func (suite *KeeperTestSuite) TestTraceTx() { // increase nonce to avoid address collision vmdb := suite.StateDB() vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1) - _, err := vmdb.Commit() - suite.Require().NoError(err) + suite.Require().NoError(vmdb.Commit()) chainID := suite.app.EvmKeeper.ChainID() nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address) @@ -1062,8 +1057,7 @@ func (suite *KeeperTestSuite) TestTraceBlock() { // increase nonce to avoid address collision vmdb := suite.StateDB() vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1) - _, err := vmdb.Commit() - suite.Require().NoError(err) + suite.Require().NoError(vmdb.Commit()) contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) suite.Commit() diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index 445e9b2583..1bf3c45f46 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -451,8 +451,7 @@ func (suite *KeeperTestSuite) TestSuicide() { db.SetState(suite.address, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), common.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) } - _, err := db.Commit() - suite.Require().NoError(err) + suite.Require().NoError(db.Commit()) db = suite.StateDB() // Generate 2nd address @@ -475,8 +474,7 @@ func (suite *KeeperTestSuite) TestSuicide() { suite.Require().Equal(true, db.HasSuicided(suite.address)) // Commit state - _, err = db.Commit() - suite.Require().NoError(err) + suite.Require().NoError(db.Commit()) db = suite.StateDB() // Check code is deleted diff --git a/x/evm/keeper/utils_test.go b/x/evm/keeper/utils_test.go index 47207d842f..f14e119f29 100644 --- a/x/evm/keeper/utils_test.go +++ b/x/evm/keeper/utils_test.go @@ -207,7 +207,7 @@ func (suite *KeeperTestSuite) TestCheckSenderBalance() { vmdb.AddBalance(suite.address, hundredInt.BigInt()) balance := vmdb.GetBalance(suite.address) suite.Require().Equal(balance, hundredInt.BigInt()) - _, err := vmdb.Commit() + err := vmdb.Commit() suite.Require().NoError(err, "Unexpected error while committing to vmdb: %d", err) for i, tc := range testCases { @@ -467,7 +467,7 @@ func (suite *KeeperTestSuite) TestVerifyFeeAndDeductTxCostsFromUserBalance() { balance := vmdb.GetBalance(suite.address) suite.Require().Equal(balance, hundredInt.BigInt()) } - _, err := vmdb.Commit() + err := vmdb.Commit() suite.Require().NoError(err, "Unexpected error while committing to vmdb: %d", err) tx := evmtypes.NewTx(zeroInt.BigInt(), 1, &suite.address, amount, tc.gasLimit, gasPrice, gasFeeCap, gasTipCap, nil, tc.accessList) From ac011e65b6d3eba3b16ea7c219d3a27ab56cdf6b Mon Sep 17 00:00:00 2001 From: mmsqe Date: Thu, 13 Apr 2023 22:05:46 +0800 Subject: [PATCH 38/41] revert cache store refactor --- app/ante/eth.go | 8 +- app/ante/eth_test.go | 2 +- app/ante/handler_options.go | 4 +- app/ante/utils_test.go | 3 +- app/app.go | 10 +- go.mod | 2 +- store/cachekv/README.md | 140 ----- store/cachekv/bench_helper_test.go | 44 -- store/cachekv/benchmark_test.go | 133 ----- store/cachekv/internal/btree.go | 101 ---- store/cachekv/internal/btree_test.go | 208 ------- store/cachekv/internal/memiterator.go | 119 ---- store/cachekv/internal/mergeiterator.go | 235 -------- store/cachekv/store.go | 179 ------ store/cachekv/store_bench_test.go | 149 ----- store/cachekv/store_test.go | 707 ------------------------ store/cachemulti/store.go | 191 ------- store/cachemulti/store_test.go | 25 - tests/importer/importer_test.go | 2 +- x/evm/handler_test.go | 2 +- x/evm/keeper/grpc_query_test.go | 2 +- x/evm/keeper/hooks_test.go | 2 +- x/evm/keeper/keeper.go | 6 - x/evm/keeper/keeper_test.go | 2 +- x/evm/keeper/state_transition.go | 2 +- x/evm/keeper/statedb_test.go | 2 +- x/evm/statedb/interfaces.go | 7 - x/evm/statedb/statedb.go | 30 +- x/evm/statedb/statedb_test.go | 38 +- 29 files changed, 41 insertions(+), 2314 deletions(-) delete mode 100644 store/cachekv/README.md delete mode 100644 store/cachekv/bench_helper_test.go delete mode 100644 store/cachekv/benchmark_test.go delete mode 100644 store/cachekv/internal/btree.go delete mode 100644 store/cachekv/internal/btree_test.go delete mode 100644 store/cachekv/internal/memiterator.go delete mode 100644 store/cachekv/internal/mergeiterator.go delete mode 100644 store/cachekv/store.go delete mode 100644 store/cachekv/store_bench_test.go delete mode 100644 store/cachekv/store_test.go delete mode 100644 store/cachemulti/store.go delete mode 100644 store/cachemulti/store_test.go diff --git a/app/ante/eth.go b/app/ante/eth.go index 0d058a6e60..219c539df6 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -22,7 +22,6 @@ import ( errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" @@ -247,12 +246,11 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula // context rules. type CanTransferDecorator struct { evmKeeper EVMKeeper - keys map[string]*storetypes.KVStoreKey } // NewCanTransferDecorator creates a new CanTransferDecorator instance. -func NewCanTransferDecorator(evmKeeper EVMKeeper, keys map[string]*storetypes.KVStoreKey) CanTransferDecorator { - return CanTransferDecorator{evmKeeper, keys} +func NewCanTransferDecorator(evmKeeper EVMKeeper) CanTransferDecorator { + return CanTransferDecorator{evmKeeper} } // AnteHandle creates an EVM from the message and calls the BlockContext CanTransfer function to @@ -302,7 +300,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate BaseFee: baseFee, } - stateDB := statedb.New(ctx, ctd.keys, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) + stateDB := statedb.New(ctx, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB) // check that caller has enough balance to cover asset transfer for **topmost** call diff --git a/app/ante/eth_test.go b/app/ante/eth_test.go index c63e78b395..0fb9ea8b20 100644 --- a/app/ante/eth_test.go +++ b/app/ante/eth_test.go @@ -318,7 +318,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() { } func (suite AnteTestSuite) TestCanTransferDecorator() { - dec := ante.NewCanTransferDecorator(suite.app.EvmKeeper, suite.app.GetKeys()) + dec := ante.NewCanTransferDecorator(suite.app.EvmKeeper) addr, privKey := tests.NewAddrKey() diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 775add7cb8..ab5c1ca179 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -27,7 +27,6 @@ import ( ibcante "github.com/cosmos/ibc-go/v6/modules/core/ante" ibckeeper "github.com/cosmos/ibc-go/v6/modules/core/keeper" - storetypes "github.com/cosmos/cosmos-sdk/store/types" evmtypes "github.com/evmos/ethermint/x/evm/types" ) @@ -45,7 +44,6 @@ type HandlerOptions struct { MaxTxGasWanted uint64 ExtensionOptionChecker ante.ExtensionOptionChecker TxFeeChecker ante.TxFeeChecker - Keys map[string]*storetypes.KVStoreKey DisabledAuthzMsgs []string } @@ -76,7 +74,7 @@ func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler { NewEthValidateBasicDecorator(options.EvmKeeper), NewEthSigVerificationDecorator(options.EvmKeeper), NewEthAccountVerificationDecorator(options.AccountKeeper, options.EvmKeeper), - NewCanTransferDecorator(options.EvmKeeper, options.Keys), + NewCanTransferDecorator(options.EvmKeeper), NewEthGasConsumeDecorator(options.EvmKeeper, options.MaxTxGasWanted), NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator. NewGasWantedDecorator(options.EvmKeeper, options.FeeMarketKeeper), diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 9f25a64ce8..a5fabcd9ac 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -75,7 +75,7 @@ type AnteTestSuite struct { const TestGasLimit uint64 = 100000 func (suite *AnteTestSuite) StateDB() *statedb.StateDB { - return statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) + return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) } func (suite *AnteTestSuite) SetupTest() { @@ -141,7 +141,6 @@ func (suite *AnteTestSuite) SetupTest() { FeeMarketKeeper: suite.app.FeeMarketKeeper, SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), SigGasConsumer: ante.DefaultSigVerificationGasConsumer, - Keys: suite.app.GetKeys(), DisabledAuthzMsgs: []string{ sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}), sdk.MsgTypeURL(&vestingtypes.MsgCreateVestingAccount{}), diff --git a/app/app.go b/app/app.go index f91a52b42e..4a05f14e74 100644 --- a/app/app.go +++ b/app/app.go @@ -419,7 +419,7 @@ func NewEthermintApp( app.EvmKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.FeeMarketKeeper, - tracer, evmSs, keys, + tracer, evmSs, ) // Create IBC Keeper @@ -664,7 +664,6 @@ func (app *EthermintApp) setAnteHandler(txConfig client.TxConfig, maxGasWanted u MaxTxGasWanted: maxGasWanted, ExtensionOptionChecker: ethermint.HasDynamicFeeExtensionOption, TxFeeChecker: ante.NewDynamicFeeChecker(app.EvmKeeper), - Keys: app.keys, DisabledAuthzMsgs: []string{ sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}), sdk.MsgTypeURL(&vestingtypes.MsgCreateVestingAccount{}), @@ -758,13 +757,6 @@ func (app *EthermintApp) InterfaceRegistry() types.InterfaceRegistry { return app.interfaceRegistry } -// GetKeys returns the KVStoreKeys. -// -// NOTE: This is solely to be used for testing purposes. -func (app *EthermintApp) GetKeys() map[string]*storetypes.KVStoreKey { - return app.keys -} - // GetKey returns the KVStoreKey for the provided store key. // // NOTE: This is solely to be used for testing purposes. diff --git a/go.mod b/go.mod index 3cbf4506a3..c78dc756bb 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,6 @@ require ( github.com/stretchr/testify v1.8.2 github.com/tendermint/tendermint v0.34.27 github.com/tendermint/tm-db v0.6.7 - github.com/tidwall/btree v1.5.0 github.com/tidwall/gjson v1.14.4 github.com/tidwall/sjson v1.2.5 github.com/tyler-smith/go-bip39 v1.1.0 @@ -169,6 +168,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tendermint/go-amino v0.16.0 // indirect + github.com/tidwall/btree v1.5.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect diff --git a/store/cachekv/README.md b/store/cachekv/README.md deleted file mode 100644 index 66f0916dea..0000000000 --- a/store/cachekv/README.md +++ /dev/null @@ -1,140 +0,0 @@ -# CacheKVStore specification - -A `CacheKVStore` is cache wrapper for a `KVStore`. It extends the operations of the `KVStore` to work with a write-back cache, allowing for reduced I/O operations and more efficient disposing of changes (e.g. after processing a failed transaction). - -The core goals the CacheKVStore seeks to solve are: - -* Buffer all writes to the parent store, so they can be dropped if they need to be reverted -* Allow iteration over contiguous spans of keys -* Act as a cache, improving access time for reads that have already been done (by replacing tree access with hashtable access, avoiding disk I/O) - * Note: We actually fail to achieve this for iteration right now - * Note: Need to consider this getting too large and dropping some cached reads -* Make subsequent reads account for prior buffered writes -* Write all buffered changes to the parent store - -We should revisit these goals with time (for instance it's unclear that all disk writes need to be buffered to the end of the block), but this is the current status. - -## Types and Structs - -```go -type Store struct { - mtx sync.Mutex - cache map[string]*cValue - deleted map[string]struct{} - unsortedCache map[string]struct{} - sortedCache *dbm.MemDB // always ascending sorted - parent types.KVStore -} -``` - -The Store struct wraps the underlying `KVStore` (`parent`) with additional data structures for implementing the cache. Mutex is used as IAVL trees (the `KVStore` in application) are not safe for concurrent use. - -### `cache` - -The main mapping of key-value pairs stored in cache. This map contains both keys that are cached from read operations as well as ‘dirty’ keys which map to a value that is potentially different than what is in the underlying `KVStore`. - -Values that are mapped to in `cache` are wrapped in a `cValue` struct, which contains the value and a boolean flag (`dirty`) representing whether the value has been written since the last write-back to `parent`. - -```go -type cValue struct { - value []byte - dirty bool -} -``` - -### `deleted` - -Key-value pairs that are to be deleted from `parent` are stored in the `deleted` map. Keys are mapped to an empty struct to implement a set. - -### `unsortedCache` - -Similar to `deleted`, this is a set of keys that are dirty and will need to be updated in the parent `KVStore` upon a write. Keys are mapped to an empty struct to implement a set. - -### `sortedCache` - -A database that will be populated by the keys in `unsortedCache` during iteration over the cache. The keys are always held in sorted order. - -## CRUD Operations and Writing - -The `Set`, `Get`, and `Delete` functions all call `setCacheValue()`, which is the only entry point to mutating `cache` (besides `Write()`, which clears it). - -`setCacheValue()` inserts a key-value pair into `cache`. Two boolean parameters, `deleted` and `dirty`, are passed in to flag whether the inserted key should also be inserted into the `deleted` and `dirty` sets. Keys will be removed from the `deleted` set if they are written to after being deleted. - -### `Get` - -`Get` first attempts to return the value from `cache`. If the key does not exist in `cache`, `parent.Get()` is called instead. This value from the parent is passed into `setCacheValue()` with `deleted=false` and `dirty=false`. - -### `Has` - -`Has` returns true if `Get` returns a non-nil value. As a result of calling `Get`, it may mutate the cache by caching the read. - -### `Set` - -New values are written by setting or updating the value of a key in `cache`. `Set` does not write to `parent`. - -Calls `setCacheValue()` with `deleted=false` and `dirty=true`. - -### `Delete` - -A value being deleted from the `KVStore` is represented with a `nil` value in `cache`, and an insertion of the key into the `deleted` set. `Delete` does not write to `parent`. - -Calls `setCacheValue()` with `deleted=true` and `dirty=true`. - -### `Write` - -Key-value pairs in the cache are written to `parent` in ascending order of their keys. - -A slice of all dirty keys in `cache` is made, then sorted in increasing order. These keys are iterated over to update `parent`. - -If a key is marked for deletion (checked with `isDeleted()`), then `parent.Delete()` is called. Otherwise, `parent.Set()` is called to update the underlying `KVStore` with the value in cache. - -## Iteration - -Efficient iteration over keys in `KVStore` is important for generating Merkle range proofs. Iteration over `CacheKVStore` requires producing all key-value pairs from the underlying `KVStore` while taking into account updated values from the cache. - -In the current implementation, there is no guarantee that all values in `parent` have been cached. As a result, iteration is achieved by interleaved iteration through both `parent` and the cache (failing to actually benefit from caching). - -[cacheMergeIterator](https://github.com/cosmos/cosmos-sdk/blob/d8391cb6796d770b02448bee70b865d824e43449/store/cachekv/mergeiterator.go) implements functions to provide a single iterator with an input of iterators over `parent` and the cache. This iterator iterates over keys from both iterators in a shared lexicographic order, and overrides the value provided by the parent iterator if the same key is dirty or deleted in the cache. - -### Implementation Overview - -Iterators over `parent` and the cache are generated and passed into `cacheMergeIterator`, which returns a single, interleaved iterator. Implementation of the `parent` iterator is up to the underlying `KVStore`. The remainder of this section covers the generation of the cache iterator. - -Recall that `unsortedCache` is an unordered set of dirty cache keys. Our goal is to construct an ordered iterator over cache keys that fall within the `start` and `end` bounds requested. - -Generating the cache iterator can be decomposed into four parts: - -1. Finding all keys that exist in the range we are iterating over -2. Sorting this list of keys -3. Inserting these keys into `sortedCache` and removing them from `unsortedCache` -4. Returning an iterator over `sortedCache` with the desired range - -Currently, the implementation for the first two parts is split into two cases, depending on the size of the unsorted cache. The two cases are as follows. - -If the size of `unsortedCache` is less than `minSortSize` (currently 1024), a linear time approach is taken to search over keys. - -```go -n := len(store.unsortedCache) -unsorted := make([]*kv.Pair, 0) - -if n < minSortSize { - for key := range store.unsortedCache { - if dbm.IsKeyInDomain(conv.UnsafeStrToBytes(key), start, end) { - cacheValue := store.cache[key] - unsorted = append(unsorted, &kv.Pair{Key: []byte(key), Value: cacheValue.value}) - } - } - store.clearUnsortedCacheSubset(unsorted, stateUnsorted) - return -} -``` - -Here, we iterate through all the keys in `unsortedCache` (i.e., the dirty cache keys), collecting those within the requested range in an unsorted slice called `unsorted`. - -At this point, part 3. is achieved in `clearUnsortedCacheSubset()`. This function iterates through `unsorted`, removing each key from `unsortedCache`. Afterwards, `unsorted` is sorted. Lastly, it iterates through the now sorted slice, inserting key-value pairs into `sortedCache`. Any key marked for deletion is mapped to an arbitrary value (`[]byte{}`). - -In the case that the size of `unsortedCache` is larger than `minSortSize`, a linear time approach to finding keys within the desired range is too slow to use. Instead, a slice of all keys in `unsortedCache` is sorted, and binary search is used to find the beginning and ending indices of the desired range. This produces an already-sorted slice that is passed into the same `clearUnsortedCacheSubset()` function. An iota identifier (`sortedState`) is used to skip the sorting step in the function. - -Finally, part 4. is achieved with `memIterator`, which implements an iterator over the items in `sortedCache`. - -As of [PR #12885](https://github.com/cosmos/cosmos-sdk/pull/12885), an optimization to the binary search case mitigates the overhead of sorting the entirety of the key set in `unsortedCache`. To avoid wasting the compute spent sorting, we should ensure that a reasonable amount of values are removed from `unsortedCache`. If the length of the range for iteration is less than `minSortedCache`, we widen the range of values for removal from `unsortedCache` to be up to `minSortedCache` in length. This amortizes the cost of processing elements across multiple calls. \ No newline at end of file diff --git a/store/cachekv/bench_helper_test.go b/store/cachekv/bench_helper_test.go deleted file mode 100644 index fe5be27fab..0000000000 --- a/store/cachekv/bench_helper_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package cachekv_test - -import "crypto/rand" - -func randSlice(sliceSize int) []byte { - bz := make([]byte, sliceSize) - _, _ = rand.Read(bz) - return bz -} - -func incrementByteSlice(bz []byte) { - for index := len(bz) - 1; index >= 0; index-- { - if bz[index] < 255 { - bz[index]++ - break - } else { - bz[index] = 0 - } - } -} - -// Generate many keys starting at startKey, and are in sequential order -func generateSequentialKeys(startKey []byte, numKeys int) [][]byte { - toReturn := make([][]byte, 0, numKeys) - cur := make([]byte, len(startKey)) - copy(cur, startKey) - for i := 0; i < numKeys; i++ { - newKey := make([]byte, len(startKey)) - copy(newKey, cur) - toReturn = append(toReturn, newKey) - incrementByteSlice(cur) - } - return toReturn -} - -// Generate many random, unsorted keys -func generateRandomKeys(keySize int, numKeys int) [][]byte { - toReturn := make([][]byte, 0, numKeys) - for i := 0; i < numKeys; i++ { - newKey := randSlice(keySize) - toReturn = append(toReturn, newKey) - } - return toReturn -} diff --git a/store/cachekv/benchmark_test.go b/store/cachekv/benchmark_test.go deleted file mode 100644 index cefb467a91..0000000000 --- a/store/cachekv/benchmark_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package cachekv_test - -import ( - fmt "fmt" - "testing" - - "github.com/cosmos/cosmos-sdk/store/dbadapter" - "github.com/evmos/ethermint/store/cachekv" - "github.com/stretchr/testify/require" - dbm "github.com/tendermint/tm-db" -) - -func DoBenchmarkDeepCacheStack(b *testing.B, depth int) { - db := dbm.NewMemDB() - initialStore := cachekv.NewStore(dbadapter.Store{DB: db}) - - nItems := 20 - for i := 0; i < nItems; i++ { - initialStore.Set([]byte(fmt.Sprintf("hello%03d", i)), []byte{0}) - } - - var stack CacheStack - stack.Reset(initialStore) - - for i := 0; i < depth; i++ { - stack.Snapshot() - - store := stack.CurrentStore() - store.Set([]byte(fmt.Sprintf("hello%03d", i)), []byte{byte(i)}) - } - - store := stack.CurrentStore() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - it := store.Iterator(nil, nil) - items := make([][]byte, 0, nItems) - for ; it.Valid(); it.Next() { - items = append(items, it.Key()) - it.Value() - } - it.Close() - require.Equal(b, nItems, len(items)) - } -} - -func BenchmarkDeepCacheStack1(b *testing.B) { - DoBenchmarkDeepCacheStack(b, 1) -} - -func BenchmarkDeepCacheStack3(b *testing.B) { - DoBenchmarkDeepCacheStack(b, 3) -} - -func BenchmarkDeepCacheStack10(b *testing.B) { - DoBenchmarkDeepCacheStack(b, 10) -} - -func BenchmarkDeepCacheStack13(b *testing.B) { - DoBenchmarkDeepCacheStack(b, 13) -} - -// CacheStack manages a stack of nested cache store to -// support the evm `StateDB`'s `Snapshot` and `RevertToSnapshot` methods. -type CacheStack struct { - initialStore *cachekv.Store - // Context of the initial state before transaction execution. - // It's the context used by `StateDB.CommitedState`. - cacheStores []*cachekv.Store -} - -// CurrentContext returns the top context of cached stack, -// if the stack is empty, returns the initial context. -func (cs *CacheStack) CurrentStore() *cachekv.Store { - l := len(cs.cacheStores) - if l == 0 { - return cs.initialStore - } - return cs.cacheStores[l-1] -} - -// Reset sets the initial context and clear the cache context stack. -func (cs *CacheStack) Reset(initialStore *cachekv.Store) { - cs.initialStore = initialStore - cs.cacheStores = nil -} - -// IsEmpty returns true if the cache context stack is empty. -func (cs *CacheStack) IsEmpty() bool { - return len(cs.cacheStores) == 0 -} - -// Commit commits all the cached contexts from top to bottom in order and clears the stack by setting an empty slice of cache contexts. -func (cs *CacheStack) Commit() { - // commit in order from top to bottom - for i := len(cs.cacheStores) - 1; i >= 0; i-- { - cs.cacheStores[i].Write() - } - cs.cacheStores = nil -} - -// CommitToRevision commit the cache after the target revision, -// to improve efficiency of db operations. -func (cs *CacheStack) CommitToRevision(target int) error { - if target < 0 || target >= len(cs.cacheStores) { - return fmt.Errorf("snapshot index %d out of bound [%d..%d)", target, 0, len(cs.cacheStores)) - } - - // commit in order from top to bottom - for i := len(cs.cacheStores) - 1; i > target; i-- { - cs.cacheStores[i].Write() - } - cs.cacheStores = cs.cacheStores[0 : target+1] - - return nil -} - -// Snapshot pushes a new cached context to the stack, -// and returns the index of it. -func (cs *CacheStack) Snapshot() int { - cs.cacheStores = append(cs.cacheStores, cs.CurrentStore().Clone()) - return len(cs.cacheStores) - 1 -} - -// RevertToSnapshot pops all the cached contexts after the target index (inclusive). -// the target should be snapshot index returned by `Snapshot`. -// This function panics if the index is out of bounds. -func (cs *CacheStack) RevertToSnapshot(target int) { - if target < 0 || target >= len(cs.cacheStores) { - panic(fmt.Errorf("snapshot index %d out of bound [%d..%d)", target, 0, len(cs.cacheStores))) - } - cs.cacheStores = cs.cacheStores[:target] -} diff --git a/store/cachekv/internal/btree.go b/store/cachekv/internal/btree.go deleted file mode 100644 index d825a365de..0000000000 --- a/store/cachekv/internal/btree.go +++ /dev/null @@ -1,101 +0,0 @@ -package internal - -import ( - "bytes" - "errors" - - "github.com/cosmos/cosmos-sdk/store/types" - "github.com/tidwall/btree" -) - -const ( - // The approximate number of items and children per B-tree node. Tuned with benchmarks. - // copied from memdb. - bTreeDegree = 32 -) - -var errKeyEmpty = errors.New("key cannot be empty") - -// BTree implements the sorted cache for cachekv store, -// we don't use MemDB here because cachekv is used extensively in sdk core path, -// we need it to be as fast as possible, while `MemDB` is mainly used as a mocking db in unit tests. -// -// We choose tidwall/btree over google/btree here because it provides API to implement step iterator directly. -type BTree struct { - tree *btree.BTreeG[item] -} - -// NewBTree creates a wrapper around `btree.BTreeG`. -func NewBTree() BTree { - return BTree{ - tree: btree.NewBTreeGOptions(byKeys, btree.Options{ - Degree: bTreeDegree, - NoLocks: false, - }), - } -} - -func (bt BTree) Set(key, value []byte, dirty bool) { - bt.tree.Set(item{key: key, value: value, dirty: dirty}) -} - -func (bt BTree) Get(key []byte) ([]byte, bool) { - i, found := bt.tree.Get(newItem(key, nil)) - if !found { - return nil, false - } - return i.value, true -} - -func (bt BTree) Delete(key []byte) { - bt.tree.Delete(newItem(key, nil)) -} - -func (bt BTree) Iterator(start, end []byte) (types.Iterator, error) { - if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { - return nil, errKeyEmpty - } - return newMemIterator(start, end, bt, true), nil -} - -func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) { - if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { - return nil, errKeyEmpty - } - return newMemIterator(start, end, bt, false), nil -} - -// ScanDirtyItems iterate over the dirty entries. -func (bt BTree) ScanDirtyItems(fn func(key, value []byte)) { - bt.tree.Scan(func(item item) bool { - if item.dirty { - fn(item.key, item.value) - } - return true - }) -} - -// Copy the tree. This is a copy-on-write operation and is very fast because -// it only performs a shadowed copy. -func (bt BTree) Copy() BTree { - return BTree{ - tree: bt.tree.Copy(), - } -} - -// item is a btree item with byte slices as keys and values -type item struct { - key []byte - value []byte - dirty bool -} - -// byKeys compares the items by key -func byKeys(a, b item) bool { - return bytes.Compare(a.key, b.key) == -1 -} - -// newItem creates a new pair item. -func newItem(key, value []byte) item { - return item{key: key, value: value} -} diff --git a/store/cachekv/internal/btree_test.go b/store/cachekv/internal/btree_test.go deleted file mode 100644 index bd19449ba4..0000000000 --- a/store/cachekv/internal/btree_test.go +++ /dev/null @@ -1,208 +0,0 @@ -package internal - -import ( - "testing" - - "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" -) - -func TestGetSetDelete(t *testing.T) { - db := NewBTree() - - // A nonexistent key should return nil. - value, found := db.Get([]byte("a")) - require.Nil(t, value) - require.False(t, found) - - // Set and get a value. - db.Set([]byte("a"), []byte{0x01}, true) - db.Set([]byte("b"), []byte{0x02}, true) - value, found = db.Get([]byte("a")) - require.Equal(t, []byte{0x01}, value) - require.True(t, found) - - value, found = db.Get([]byte("b")) - require.Equal(t, []byte{0x02}, value) - require.True(t, found) - - // Deleting a non-existent value is fine. - db.Delete([]byte("x")) - - // Delete a value. - db.Delete([]byte("a")) - - value, found = db.Get([]byte("a")) - require.Nil(t, value) - require.False(t, found) - - db.Delete([]byte("b")) - - value, found = db.Get([]byte("b")) - require.Nil(t, value) - require.False(t, found) -} - -func TestDBIterator(t *testing.T) { - db := NewBTree() - - for i := 0; i < 10; i++ { - if i != 6 { // but skip 6. - db.Set(int642Bytes(int64(i)), []byte{}, true) - } - } - - // Blank iterator keys should error - _, err := db.ReverseIterator([]byte{}, nil) - require.Equal(t, errKeyEmpty, err) - _, err = db.ReverseIterator(nil, []byte{}) - require.Equal(t, errKeyEmpty, err) - - itr, err := db.Iterator(nil, nil) - require.NoError(t, err) - verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator") - - ritr, err := db.ReverseIterator(nil, nil) - require.NoError(t, err) - verifyIterator(t, ritr, []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator") - - itr, err = db.Iterator(nil, int642Bytes(0)) - require.NoError(t, err) - verifyIterator(t, itr, []int64(nil), "forward iterator to 0") - - ritr, err = db.ReverseIterator(int642Bytes(10), nil) - require.NoError(t, err) - verifyIterator(t, ritr, []int64(nil), "reverse iterator from 10 (ex)") - - itr, err = db.Iterator(int642Bytes(0), nil) - require.NoError(t, err) - verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 0") - - itr, err = db.Iterator(int642Bytes(1), nil) - require.NoError(t, err) - verifyIterator(t, itr, []int64{1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 1") - - ritr, err = db.ReverseIterator(nil, int642Bytes(10)) - require.NoError(t, err) - verifyIterator(t, ritr, - []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 10 (ex)") - - ritr, err = db.ReverseIterator(nil, int642Bytes(9)) - require.NoError(t, err) - verifyIterator(t, ritr, - []int64{8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 9 (ex)") - - ritr, err = db.ReverseIterator(nil, int642Bytes(8)) - require.NoError(t, err) - verifyIterator(t, ritr, - []int64{7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 8 (ex)") - - itr, err = db.Iterator(int642Bytes(5), int642Bytes(6)) - require.NoError(t, err) - verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 6") - - itr, err = db.Iterator(int642Bytes(5), int642Bytes(7)) - require.NoError(t, err) - verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 7") - - itr, err = db.Iterator(int642Bytes(5), int642Bytes(8)) - require.NoError(t, err) - verifyIterator(t, itr, []int64{5, 7}, "forward iterator from 5 to 8") - - itr, err = db.Iterator(int642Bytes(6), int642Bytes(7)) - require.NoError(t, err) - verifyIterator(t, itr, []int64(nil), "forward iterator from 6 to 7") - - itr, err = db.Iterator(int642Bytes(6), int642Bytes(8)) - require.NoError(t, err) - verifyIterator(t, itr, []int64{7}, "forward iterator from 6 to 8") - - itr, err = db.Iterator(int642Bytes(7), int642Bytes(8)) - require.NoError(t, err) - verifyIterator(t, itr, []int64{7}, "forward iterator from 7 to 8") - - ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(5)) - require.NoError(t, err) - verifyIterator(t, ritr, []int64{4}, "reverse iterator from 5 (ex) to 4") - - ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(6)) - require.NoError(t, err) - verifyIterator(t, ritr, - []int64{5, 4}, "reverse iterator from 6 (ex) to 4") - - ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(7)) - require.NoError(t, err) - verifyIterator(t, ritr, - []int64{5, 4}, "reverse iterator from 7 (ex) to 4") - - ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(6)) - require.NoError(t, err) - verifyIterator(t, ritr, []int64{5}, "reverse iterator from 6 (ex) to 5") - - ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(7)) - require.NoError(t, err) - verifyIterator(t, ritr, []int64{5}, "reverse iterator from 7 (ex) to 5") - - ritr, err = db.ReverseIterator(int642Bytes(6), int642Bytes(7)) - require.NoError(t, err) - verifyIterator(t, ritr, - []int64(nil), "reverse iterator from 7 (ex) to 6") - - ritr, err = db.ReverseIterator(int642Bytes(10), nil) - require.NoError(t, err) - verifyIterator(t, ritr, []int64(nil), "reverse iterator to 10") - - ritr, err = db.ReverseIterator(int642Bytes(6), nil) - require.NoError(t, err) - verifyIterator(t, ritr, []int64{9, 8, 7}, "reverse iterator to 6") - - ritr, err = db.ReverseIterator(int642Bytes(5), nil) - require.NoError(t, err) - verifyIterator(t, ritr, []int64{9, 8, 7, 5}, "reverse iterator to 5") - - ritr, err = db.ReverseIterator(int642Bytes(8), int642Bytes(9)) - require.NoError(t, err) - verifyIterator(t, ritr, []int64{8}, "reverse iterator from 9 (ex) to 8") - - ritr, err = db.ReverseIterator(int642Bytes(2), int642Bytes(4)) - require.NoError(t, err) - verifyIterator(t, ritr, - []int64{3, 2}, "reverse iterator from 4 (ex) to 2") - - ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(2)) - require.NoError(t, err) - verifyIterator(t, ritr, - []int64(nil), "reverse iterator from 2 (ex) to 4") - - // Ensure that the iterators don't panic with an empty database. - db2 := NewBTree() - - itr, err = db2.Iterator(nil, nil) - require.NoError(t, err) - verifyIterator(t, itr, nil, "forward iterator with empty db") - - ritr, err = db2.ReverseIterator(nil, nil) - require.NoError(t, err) - verifyIterator(t, ritr, nil, "reverse iterator with empty db") -} - -func verifyIterator(t *testing.T, itr types.Iterator, expected []int64, msg string) { - i := 0 - for itr.Valid() { - key := itr.Key() - require.Equal(t, expected[i], bytes2Int64(key), "iterator: %d mismatches", i) - itr.Next() - i++ - } - require.Equal(t, i, len(expected), "expected to have fully iterated over all the elements in iter") - require.NoError(t, itr.Close()) -} - -func int642Bytes(i int64) []byte { - return sdk.Uint64ToBigEndian(uint64(i)) -} - -func bytes2Int64(buf []byte) int64 { - return int64(sdk.BigEndianToUint64(buf)) -} diff --git a/store/cachekv/internal/memiterator.go b/store/cachekv/internal/memiterator.go deleted file mode 100644 index b7960945ee..0000000000 --- a/store/cachekv/internal/memiterator.go +++ /dev/null @@ -1,119 +0,0 @@ -package internal - -import ( - "bytes" - "errors" - - "github.com/cosmos/cosmos-sdk/store/types" - "github.com/tidwall/btree" -) - -var _ types.Iterator = (*memIterator)(nil) - -// memIterator iterates over iterKVCache items. -// if value is nil, means it was deleted. -// Implements Iterator. -type memIterator struct { - iter btree.GenericIter[item] - - start []byte - end []byte - ascending bool - valid bool -} - -func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator { - iter := items.tree.Iter() - var valid bool - if ascending { - if start != nil { - valid = iter.Seek(newItem(start, nil)) - } else { - valid = iter.First() - } - } else { - if end != nil { - valid = iter.Seek(newItem(end, nil)) - if !valid { - valid = iter.Last() - } else { - // end is exclusive - valid = iter.Prev() - } - } else { - valid = iter.Last() - } - } - - mi := &memIterator{ - iter: iter, - start: start, - end: end, - ascending: ascending, - valid: valid, - } - - if mi.valid { - mi.valid = mi.keyInRange(mi.Key()) - } - - return mi -} - -func (mi *memIterator) Domain() (start []byte, end []byte) { - return mi.start, mi.end -} - -func (mi *memIterator) Close() error { - mi.iter.Release() - return nil -} - -func (mi *memIterator) Error() error { - if !mi.Valid() { - return errors.New("invalid memIterator") - } - return nil -} - -func (mi *memIterator) Valid() bool { - return mi.valid -} - -func (mi *memIterator) Next() { - mi.assertValid() - - if mi.ascending { - mi.valid = mi.iter.Next() - } else { - mi.valid = mi.iter.Prev() - } - - if mi.valid { - mi.valid = mi.keyInRange(mi.Key()) - } -} - -func (mi *memIterator) keyInRange(key []byte) bool { - if mi.ascending && mi.end != nil && bytes.Compare(key, mi.end) >= 0 { - return false - } - if !mi.ascending && mi.start != nil && bytes.Compare(key, mi.start) < 0 { - return false - } - return true -} - -func (mi *memIterator) Key() []byte { - return mi.iter.Item().key -} - -func (mi *memIterator) Value() []byte { - return mi.iter.Item().value -} - -func (mi *memIterator) assertValid() { - if err := mi.Error(); err != nil { - panic(err) - } -} diff --git a/store/cachekv/internal/mergeiterator.go b/store/cachekv/internal/mergeiterator.go deleted file mode 100644 index 293bc968e7..0000000000 --- a/store/cachekv/internal/mergeiterator.go +++ /dev/null @@ -1,235 +0,0 @@ -package internal - -import ( - "bytes" - "errors" - - "github.com/cosmos/cosmos-sdk/store/types" -) - -// cacheMergeIterator merges a parent Iterator and a cache Iterator. -// The cache iterator may return nil keys to signal that an item -// had been deleted (but not deleted in the parent). -// If the cache iterator has the same key as the parent, the -// cache shadows (overrides) the parent. -// -// TODO: Optimize by memoizing. -type cacheMergeIterator struct { - parent types.Iterator - cache types.Iterator - ascending bool - - valid bool -} - -var _ types.Iterator = (*cacheMergeIterator)(nil) - -func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) *cacheMergeIterator { //nolint:revive - iter := &cacheMergeIterator{ - parent: parent, - cache: cache, - ascending: ascending, - } - - iter.valid = iter.skipUntilExistsOrInvalid() - return iter -} - -// Domain implements Iterator. -// Returns parent domain because cache and parent domains are the same. -func (iter *cacheMergeIterator) Domain() (start, end []byte) { - return iter.parent.Domain() -} - -// Valid implements Iterator. -func (iter *cacheMergeIterator) Valid() bool { - return iter.valid -} - -// Next implements Iterator -func (iter *cacheMergeIterator) Next() { - iter.assertValid() - - switch { - case !iter.parent.Valid(): - // If parent is invalid, get the next cache item. - iter.cache.Next() - case !iter.cache.Valid(): - // If cache is invalid, get the next parent item. - iter.parent.Next() - default: - // Both are valid. Compare keys. - keyP, keyC := iter.parent.Key(), iter.cache.Key() - switch iter.compare(keyP, keyC) { - case -1: // parent < cache - iter.parent.Next() - case 0: // parent == cache - iter.parent.Next() - iter.cache.Next() - case 1: // parent > cache - iter.cache.Next() - } - } - iter.valid = iter.skipUntilExistsOrInvalid() -} - -// Key implements Iterator -func (iter *cacheMergeIterator) Key() []byte { - iter.assertValid() - - // If parent is invalid, get the cache key. - if !iter.parent.Valid() { - return iter.cache.Key() - } - - // If cache is invalid, get the parent key. - if !iter.cache.Valid() { - return iter.parent.Key() - } - - // Both are valid. Compare keys. - keyP, keyC := iter.parent.Key(), iter.cache.Key() - - cmp := iter.compare(keyP, keyC) - switch cmp { - case -1: // parent < cache - return keyP - case 0: // parent == cache - return keyP - case 1: // parent > cache - return keyC - default: - panic("invalid compare result") - } -} - -// Value implements Iterator -func (iter *cacheMergeIterator) Value() []byte { - iter.assertValid() - - // If parent is invalid, get the cache value. - if !iter.parent.Valid() { - return iter.cache.Value() - } - - // If cache is invalid, get the parent value. - if !iter.cache.Valid() { - return iter.parent.Value() - } - - // Both are valid. Compare keys. - keyP, keyC := iter.parent.Key(), iter.cache.Key() - - cmp := iter.compare(keyP, keyC) - switch cmp { - case -1: // parent < cache - return iter.parent.Value() - case 0: // parent == cache - return iter.cache.Value() - case 1: // parent > cache - return iter.cache.Value() - default: - panic("invalid comparison result") - } -} - -// Close implements Iterator -func (iter *cacheMergeIterator) Close() error { - err1 := iter.cache.Close() - if err := iter.parent.Close(); err != nil { - return err - } - - return err1 -} - -// Error returns an error if the cacheMergeIterator is invalid defined by the -// Valid method. -func (iter *cacheMergeIterator) Error() error { - if !iter.Valid() { - return errors.New("invalid cacheMergeIterator") - } - - return nil -} - -// If not valid, panics. -// NOTE: May have side-effect of iterating over cache. -func (iter *cacheMergeIterator) assertValid() { - if err := iter.Error(); err != nil { - panic(err) - } -} - -// Like bytes.Compare but opposite if not ascending. -func (iter *cacheMergeIterator) compare(a, b []byte) int { - if iter.ascending { - return bytes.Compare(a, b) - } - - return bytes.Compare(a, b) * -1 -} - -// Skip all delete-items from the cache w/ `key < until`. After this function, -// current cache item is a non-delete-item, or `until <= key`. -// If the current cache item is not a delete item, does nothing. -// If `until` is nil, there is no limit, and cache may end up invalid. -// CONTRACT: cache is valid. -func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) { - for iter.cache.Valid() && - iter.cache.Value() == nil && - (until == nil || iter.compare(iter.cache.Key(), until) < 0) { - iter.cache.Next() - } -} - -// Fast forwards cache (or parent+cache in case of deleted items) until current -// item exists, or until iterator becomes invalid. -// Returns whether the iterator is valid. -func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool { - for { - // If parent is invalid, fast-forward cache. - if !iter.parent.Valid() { - iter.skipCacheDeletes(nil) - return iter.cache.Valid() - } - // Parent is valid. - - if !iter.cache.Valid() { - return true - } - // Parent is valid, cache is valid. - - // Compare parent and cache. - keyP := iter.parent.Key() - keyC := iter.cache.Key() - - switch iter.compare(keyP, keyC) { - case -1: // parent < cache. - return true - - case 0: // parent == cache. - // Skip over if cache item is a delete. - valueC := iter.cache.Value() - if valueC == nil { - iter.parent.Next() - iter.cache.Next() - - continue - } - // Cache is not a delete. - - return true // cache exists. - case 1: // cache < parent - // Skip over if cache item is a delete. - valueC := iter.cache.Value() - if valueC == nil { - iter.skipCacheDeletes(keyP) - continue - } - // Cache is not a delete. - - return true // cache exists. - } - } -} diff --git a/store/cachekv/store.go b/store/cachekv/store.go deleted file mode 100644 index 20c6bc0b62..0000000000 --- a/store/cachekv/store.go +++ /dev/null @@ -1,179 +0,0 @@ -package cachekv - -import ( - "io" - "sync" - - "github.com/cosmos/cosmos-sdk/store/tracekv" - "github.com/cosmos/cosmos-sdk/store/types" - "github.com/evmos/ethermint/store/cachekv/internal" -) - -// Store wraps an in-memory cache around an underlying types.KVStore. -type Store struct { - mtx sync.Mutex - cache internal.BTree // always ascending sorted - parent types.KVStore -} - -var _ types.CacheKVStore = (*Store)(nil) - -// NewStore creates a new Store object -func NewStore(parent types.KVStore) *Store { - return &Store{ - cache: internal.NewBTree(), - parent: parent, - } -} - -// GetStoreType implements Store. -func (store *Store) GetStoreType() types.StoreType { - return store.parent.GetStoreType() -} - -// Clone creates a snapshot of the cache store. -// This is a copy-on-write operation and is very fast because -// it only performs a shadowed copy. -func (store *Store) Clone() *Store { - store.mtx.Lock() - defer store.mtx.Unlock() - - return &Store{ - cache: store.cache.Copy(), - parent: store.parent, - } -} - -// swapCache swap out the internal cache store and leave the current store in a unusable state. -func (store *Store) swapCache() internal.BTree { - store.mtx.Lock() - defer store.mtx.Unlock() - - cache := store.cache - store.cache = internal.BTree{} - return cache -} - -// Restore restores the store cache to a given snapshot. -func (store *Store) Restore(s types.CacheKVStore) { - cache := s.(*Store).swapCache() - - store.mtx.Lock() - defer store.mtx.Unlock() - - store.cache = cache -} - -// Get implements types.KVStore. -func (store *Store) Get(key []byte) (value []byte) { - store.mtx.Lock() - defer store.mtx.Unlock() - - types.AssertValidKey(key) - - if value, found := store.cache.Get(key); found { - return value - } - value = store.parent.Get(key) - store.setCacheValue(key, value, false) - return value -} - -// Set implements types.KVStore. -func (store *Store) Set(key []byte, value []byte) { - store.mtx.Lock() - defer store.mtx.Unlock() - - types.AssertValidKey(key) - types.AssertValidValue(value) - - store.setCacheValue(key, value, true) -} - -// Has implements types.KVStore. -func (store *Store) Has(key []byte) bool { - value := store.Get(key) - return value != nil -} - -// Delete implements types.KVStore. -func (store *Store) Delete(key []byte) { - store.mtx.Lock() - defer store.mtx.Unlock() - - types.AssertValidKey(key) - store.setCacheValue(key, nil, true) -} - -// Implements Cachetypes.KVStore. -func (store *Store) Write() { - store.mtx.Lock() - defer store.mtx.Unlock() - - store.cache.ScanDirtyItems(func(key, value []byte) { - if value == nil { - store.parent.Delete(key) - } else { - store.parent.Set(key, value) - } - }) - - store.cache = internal.NewBTree() -} - -// CacheWrap implements CacheWrapper. -func (store *Store) CacheWrap() types.CacheWrap { - return NewStore(store) -} - -// CacheWrapWithTrace implements the CacheWrapper interface. -func (store *Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap { - return NewStore(tracekv.NewStore(store, w, tc)) -} - -//---------------------------------------- -// Iteration - -// Iterator implements types.KVStore. -func (store *Store) Iterator(start, end []byte) types.Iterator { - return store.iterator(start, end, true) -} - -// ReverseIterator implements types.KVStore. -func (store *Store) ReverseIterator(start, end []byte) types.Iterator { - return store.iterator(start, end, false) -} - -func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { - store.mtx.Lock() - defer store.mtx.Unlock() - - isoSortedCache := store.cache.Copy() - - var ( - err error - parent, cache types.Iterator - ) - - if ascending { - parent = store.parent.Iterator(start, end) - cache, err = isoSortedCache.Iterator(start, end) - } else { - parent = store.parent.ReverseIterator(start, end) - cache, err = isoSortedCache.ReverseIterator(start, end) - } - if err != nil { - panic(err) - } - - return internal.NewCacheMergeIterator(parent, cache, ascending) -} - -//---------------------------------------- -// etc - -// Only entrypoint to mutate store.cache. -// A `nil` value means a deletion. -func (store *Store) setCacheValue(key, value []byte, dirty bool) { - store.cache.Set(key, value, dirty) -} diff --git a/store/cachekv/store_bench_test.go b/store/cachekv/store_bench_test.go deleted file mode 100644 index 7f4b9f57cf..0000000000 --- a/store/cachekv/store_bench_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package cachekv_test - -import ( - "testing" - - dbm "github.com/tendermint/tm-db" - - "github.com/cosmos/cosmos-sdk/store/dbadapter" - "github.com/evmos/ethermint/store/cachekv" -) - -var sink interface{} - -const defaultValueSizeBz = 1 << 12 - -// This benchmark measures the time of iterator.Next() when the parent store is blank -func benchmarkBlankParentIteratorNext(b *testing.B, keysize int) { - mem := dbadapter.Store{DB: dbm.NewMemDB()} - kvstore := cachekv.NewStore(mem) - // Use a singleton for value, to not waste time computing it - value := randSlice(defaultValueSizeBz) - // Use simple values for keys, pick a random start, - // and take next b.N keys sequentially after.] - startKey := randSlice(32) - - // Add 1 to avoid issues when b.N = 1 - keys := generateSequentialKeys(startKey, b.N+1) - for _, k := range keys { - kvstore.Set(k, value) - } - - b.ReportAllocs() - b.ResetTimer() - - iter := kvstore.Iterator(keys[0], keys[b.N]) - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - _ = iter.Key() - // deadcode elimination stub - sink = iter - } -} - -// Benchmark setting New keys to a store, where the new keys are in sequence. -func benchmarkBlankParentAppend(b *testing.B, keysize int) { - mem := dbadapter.Store{DB: dbm.NewMemDB()} - kvstore := cachekv.NewStore(mem) - - // Use a singleton for value, to not waste time computing it - value := randSlice(32) - // Use simple values for keys, pick a random start, - // and take next b.N keys sequentially after. - startKey := randSlice(32) - - keys := generateSequentialKeys(startKey, b.N) - - b.ReportAllocs() - b.ResetTimer() - - for _, k := range keys { - kvstore.Set(k, value) - } -} - -// Benchmark setting New keys to a store, where the new keys are random. -// the speed of this function does not depend on the values in the parent store -func benchmarkRandomSet(b *testing.B, keysize int) { - mem := dbadapter.Store{DB: dbm.NewMemDB()} - kvstore := cachekv.NewStore(mem) - - // Use a singleton for value, to not waste time computing it - value := randSlice(defaultValueSizeBz) - // Add 1 to avoid issues when b.N = 1 - keys := generateRandomKeys(keysize, b.N+1) - - b.ReportAllocs() - b.ResetTimer() - - for _, k := range keys { - kvstore.Set(k, value) - } - - iter := kvstore.Iterator(keys[0], keys[b.N]) - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - _ = iter.Key() - // deadcode elimination stub - sink = iter - } -} - -// Benchmark creating an iterator on a parent with D entries, -// that are all deleted in the cacheKV store. -// We essentially are benchmarking the cacheKV iterator creation & iteration times -// with the number of entries deleted in the parent. -func benchmarkIteratorOnParentWithManyDeletes(b *testing.B, numDeletes int) { - mem := dbadapter.Store{DB: dbm.NewMemDB()} - - // Use a singleton for value, to not waste time computing it - value := randSlice(32) - // Use simple values for keys, pick a random start, - // and take next D keys sequentially after. - startKey := randSlice(32) - // Add 1 to avoid issues when numDeletes = 1 - keys := generateSequentialKeys(startKey, numDeletes+1) - // setup parent db with D keys. - for _, k := range keys { - mem.Set(k, value) - } - kvstore := cachekv.NewStore(mem) - // Delete all keys from the cache KV store. - // The keys[1:] is to keep at least one entry in parent, due to a bug in the SDK iterator design. - // Essentially the iterator will never be valid, in that it should never run. - // However, this is incompatible with the for loop structure the SDK uses, hence - // causes a panic. Thus we do keys[1:]. - for _, k := range keys[1:] { - kvstore.Delete(k) - } - - b.ReportAllocs() - b.ResetTimer() - - iter := kvstore.Iterator(keys[0], keys[numDeletes]) - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - _ = iter.Key() - // deadcode elimination stub - sink = iter - } -} - -func BenchmarkBlankParentIteratorNextKeySize32(b *testing.B) { - benchmarkBlankParentIteratorNext(b, 32) -} - -func BenchmarkBlankParentAppendKeySize32(b *testing.B) { - benchmarkBlankParentAppend(b, 32) -} - -func BenchmarkSetKeySize32(b *testing.B) { - benchmarkRandomSet(b, 32) -} - -func BenchmarkIteratorOnParentWith1MDeletes(b *testing.B) { - benchmarkIteratorOnParentWithManyDeletes(b, 1_000_000) -} diff --git a/store/cachekv/store_test.go b/store/cachekv/store_test.go deleted file mode 100644 index 4527585dcc..0000000000 --- a/store/cachekv/store_test.go +++ /dev/null @@ -1,707 +0,0 @@ -package cachekv_test - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - tmrand "github.com/tendermint/tendermint/libs/rand" - dbm "github.com/tendermint/tm-db" - - "github.com/cosmos/cosmos-sdk/store/dbadapter" - "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/evmos/ethermint/store/cachekv" -) - -func newCacheKVStore() types.CacheKVStore { - // create two layer of cache store to better emulate the real world. - mem := dbadapter.Store{DB: dbm.NewMemDB()} - deliverState := cachekv.NewStore(mem) - return deliverState.Clone() -} - -func keyFmt(i int) []byte { return bz(fmt.Sprintf("key%0.8d", i)) } -func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) } - -func TestCacheKVStore(t *testing.T) { - mem := dbadapter.Store{DB: dbm.NewMemDB()} - st := cachekv.NewStore(mem) - - require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") - - // put something in mem and in cache - mem.Set(keyFmt(1), valFmt(1)) - st.Set(keyFmt(1), valFmt(1)) - require.Equal(t, valFmt(1), st.Get(keyFmt(1))) - - // update it in cache, shoudn't change mem - st.Set(keyFmt(1), valFmt(2)) - require.Equal(t, valFmt(2), st.Get(keyFmt(1))) - require.Equal(t, valFmt(1), mem.Get(keyFmt(1))) - - // write it. should change mem - st.Write() - require.Equal(t, valFmt(2), mem.Get(keyFmt(1))) - require.Equal(t, valFmt(2), st.Get(keyFmt(1))) - - // more writes and checks - st.Write() - st.Write() - require.Equal(t, valFmt(2), mem.Get(keyFmt(1))) - require.Equal(t, valFmt(2), st.Get(keyFmt(1))) - - // make a new one, check it - st = cachekv.NewStore(mem) - require.Equal(t, valFmt(2), st.Get(keyFmt(1))) - - // make a new one and delete - should not be removed from mem - st = cachekv.NewStore(mem) - st.Delete(keyFmt(1)) - require.Empty(t, st.Get(keyFmt(1))) - require.Equal(t, mem.Get(keyFmt(1)), valFmt(2)) - - // Write. should now be removed from both - st.Write() - require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") - require.Empty(t, mem.Get(keyFmt(1)), "Expected `key1` to be empty") -} - -func TestCacheKVStoreNoNilSet(t *testing.T) { - mem := dbadapter.Store{DB: dbm.NewMemDB()} - st := cachekv.NewStore(mem) - require.Panics(t, func() { st.Set([]byte("key"), nil) }, "setting a nil value should panic") - require.Panics(t, func() { st.Set(nil, []byte("value")) }, "setting a nil key should panic") - require.Panics(t, func() { st.Set([]byte(""), []byte("value")) }, "setting an empty key should panic") -} - -func TestCacheKVStoreNested(t *testing.T) { - mem := dbadapter.Store{DB: dbm.NewMemDB()} - st := cachekv.NewStore(mem) - - // set. check its there on st and not on mem. - st.Set(keyFmt(1), valFmt(1)) - require.Empty(t, mem.Get(keyFmt(1))) - require.Equal(t, valFmt(1), st.Get(keyFmt(1))) - - // make a new from st and check - st2 := cachekv.NewStore(st) - require.Equal(t, valFmt(1), st2.Get(keyFmt(1))) - - // update the value on st2, check it only effects st2 - st2.Set(keyFmt(1), valFmt(3)) - require.Equal(t, []byte(nil), mem.Get(keyFmt(1))) - require.Equal(t, valFmt(1), st.Get(keyFmt(1))) - require.Equal(t, valFmt(3), st2.Get(keyFmt(1))) - - // st2 writes to its parent, st. doesnt effect mem - st2.Write() - require.Equal(t, []byte(nil), mem.Get(keyFmt(1))) - require.Equal(t, valFmt(3), st.Get(keyFmt(1))) - - // updates mem - st.Write() - require.Equal(t, valFmt(3), mem.Get(keyFmt(1))) -} - -func TestCacheKVIteratorBounds(t *testing.T) { - st := newCacheKVStore() - - // set some items - nItems := 5 - for i := 0; i < nItems; i++ { - st.Set(keyFmt(i), valFmt(i)) - } - - // iterate over all of them - itr := st.Iterator(nil, nil) - i := 0 - for ; itr.Valid(); itr.Next() { - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(i), k) - require.Equal(t, valFmt(i), v) - i++ - } - require.Equal(t, nItems, i) - require.NoError(t, itr.Close()) - - // iterate over none - itr = st.Iterator(bz("money"), nil) - i = 0 - for ; itr.Valid(); itr.Next() { - i++ - } - require.Equal(t, 0, i) - require.NoError(t, itr.Close()) - - // iterate over lower - itr = st.Iterator(keyFmt(0), keyFmt(3)) - i = 0 - for ; itr.Valid(); itr.Next() { - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(i), k) - require.Equal(t, valFmt(i), v) - i++ - } - require.Equal(t, 3, i) - require.NoError(t, itr.Close()) - - // iterate over upper - itr = st.Iterator(keyFmt(2), keyFmt(4)) - i = 2 - for ; itr.Valid(); itr.Next() { - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(i), k) - require.Equal(t, valFmt(i), v) - i++ - } - require.Equal(t, 4, i) - require.NoError(t, itr.Close()) -} - -func TestCacheKVReverseIteratorBounds(t *testing.T) { - st := newCacheKVStore() - - // set some items - nItems := 5 - for i := 0; i < nItems; i++ { - st.Set(keyFmt(i), valFmt(i)) - } - - // iterate over all of them - itr := st.ReverseIterator(nil, nil) - i := 0 - for ; itr.Valid(); itr.Next() { - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(nItems-1-i), k) - require.Equal(t, valFmt(nItems-1-i), v) - i++ - } - require.Equal(t, nItems, i) - require.NoError(t, itr.Close()) - - // iterate over none - itr = st.ReverseIterator(bz("money"), nil) - i = 0 - for ; itr.Valid(); itr.Next() { - i++ - } - require.Equal(t, 0, i) - require.NoError(t, itr.Close()) - - // iterate over lower - end := 3 - itr = st.ReverseIterator(keyFmt(0), keyFmt(end)) - i = 0 - for ; itr.Valid(); itr.Next() { - i++ - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(end-i), k) - require.Equal(t, valFmt(end-i), v) - } - require.Equal(t, 3, i) - require.NoError(t, itr.Close()) - - // iterate over upper - end = 4 - itr = st.ReverseIterator(keyFmt(2), keyFmt(end)) - i = 0 - for ; itr.Valid(); itr.Next() { - i++ - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(end-i), k) - require.Equal(t, valFmt(end-i), v) - } - require.Equal(t, 2, i) - require.NoError(t, itr.Close()) -} - -func TestCacheKVMergeIteratorBasics(t *testing.T) { - st := newCacheKVStore() - - // set and delete an item in the cache, iterator should be empty - k, v := keyFmt(0), valFmt(0) - st.Set(k, v) - st.Delete(k) - assertIterateDomain(t, st, 0) - - // now set it and assert its there - st.Set(k, v) - assertIterateDomain(t, st, 1) - - // write it and assert its there - st.Write() - assertIterateDomain(t, st, 1) - - // remove it in cache and assert its not - st.Delete(k) - assertIterateDomain(t, st, 0) - - // write the delete and assert its not there - st.Write() - assertIterateDomain(t, st, 0) - - // add two keys and assert theyre there - k1, v1 := keyFmt(1), valFmt(1) - st.Set(k, v) - st.Set(k1, v1) - assertIterateDomain(t, st, 2) - - // write it and assert theyre there - st.Write() - assertIterateDomain(t, st, 2) - - // remove one in cache and assert its not - st.Delete(k1) - assertIterateDomain(t, st, 1) - - // write the delete and assert its not there - st.Write() - assertIterateDomain(t, st, 1) - - // delete the other key in cache and asserts its empty - st.Delete(k) - assertIterateDomain(t, st, 0) -} - -func TestCacheKVMergeIteratorDeleteLast(t *testing.T) { - st := newCacheKVStore() - - // set some items and write them - nItems := 5 - for i := 0; i < nItems; i++ { - st.Set(keyFmt(i), valFmt(i)) - } - st.Write() - - // set some more items and leave dirty - for i := nItems; i < nItems*2; i++ { - st.Set(keyFmt(i), valFmt(i)) - } - - // iterate over all of them - assertIterateDomain(t, st, nItems*2) - - // delete them all - for i := 0; i < nItems*2; i++ { - last := nItems*2 - 1 - i - st.Delete(keyFmt(last)) - assertIterateDomain(t, st, last) - } -} - -func TestCacheKVMergeIteratorDeletes(t *testing.T) { - st := newCacheKVStore() - truth := dbm.NewMemDB() - - // set some items and write them - nItems := 10 - for i := 0; i < nItems; i++ { - doOp(t, st, truth, opSet, i) - } - st.Write() - - // delete every other item, starting from 0 - for i := 0; i < nItems; i += 2 { - doOp(t, st, truth, opDel, i) - assertIterateDomainCompare(t, st, truth) - } - - // reset - st = newCacheKVStore() - truth = dbm.NewMemDB() - - // set some items and write them - for i := 0; i < nItems; i++ { - doOp(t, st, truth, opSet, i) - } - st.Write() - - // delete every other item, starting from 1 - for i := 1; i < nItems; i += 2 { - doOp(t, st, truth, opDel, i) - assertIterateDomainCompare(t, st, truth) - } -} - -func TestCacheKVMergeIteratorChunks(t *testing.T) { - st := newCacheKVStore() - - // Use the truth to check values on the merge iterator - truth := dbm.NewMemDB() - - // sets to the parent - setRange(t, st, truth, 0, 20) - setRange(t, st, truth, 40, 60) - st.Write() - - // sets to the cache - setRange(t, st, truth, 20, 40) - setRange(t, st, truth, 60, 80) - assertIterateDomainCheck(t, st, truth, []keyRange{{0, 80}}) - - // remove some parents and some cache - deleteRange(t, st, truth, 15, 25) - assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 80}}) - - // remove some parents and some cache - deleteRange(t, st, truth, 35, 45) - assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 35}, {45, 80}}) - - // write, add more to the cache, and delete some cache - st.Write() - setRange(t, st, truth, 38, 42) - deleteRange(t, st, truth, 40, 43) - assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 35}, {38, 40}, {45, 80}}) -} - -func TestCacheKVMergeIteratorDomain(t *testing.T) { - st := newCacheKVStore() - - itr := st.Iterator(nil, nil) - start, end := itr.Domain() - require.Equal(t, start, end) - require.NoError(t, itr.Close()) - - itr = st.Iterator(keyFmt(40), keyFmt(60)) - start, end = itr.Domain() - require.Equal(t, keyFmt(40), start) - require.Equal(t, keyFmt(60), end) - require.NoError(t, itr.Close()) - - start, end = st.ReverseIterator(keyFmt(0), keyFmt(80)).Domain() - require.Equal(t, keyFmt(0), start) - require.Equal(t, keyFmt(80), end) -} - -func TestCacheKVMergeIteratorRandom(t *testing.T) { - st := newCacheKVStore() - truth := dbm.NewMemDB() - - start, end := 25, 975 - max := 1000 - setRange(t, st, truth, start, end) - - // do an op, test the iterator - for i := 0; i < 2000; i++ { - doRandomOp(t, st, truth, max) - assertIterateDomainCompare(t, st, truth) - } -} - -func TestNilEndIterator(t *testing.T) { - const SIZE = 3000 - - tests := []struct { - name string - write bool - startIndex int - end []byte - }{ - {name: "write=false, end=nil", write: false, end: nil, startIndex: 1000}, - {name: "write=false, end=nil; full key scan", write: false, end: nil, startIndex: 2000}, - {name: "write=true, end=nil", write: true, end: nil, startIndex: 1000}, - {name: "write=false, end=non-nil", write: false, end: keyFmt(3000), startIndex: 1000}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - st := newCacheKVStore() - - for i := 0; i < SIZE; i++ { - kstr := keyFmt(i) - st.Set(kstr, valFmt(i)) - } - - if tt.write { - st.Write() - } - - itr := st.Iterator(keyFmt(tt.startIndex), tt.end) - i := tt.startIndex - j := 0 - for itr.Valid() { - require.Equal(t, keyFmt(i), itr.Key()) - require.Equal(t, valFmt(i), itr.Value()) - itr.Next() - i++ - j++ - } - - require.Equal(t, SIZE-tt.startIndex, j) - require.NoError(t, itr.Close()) - }) - } -} - -// TestIteratorDeadlock demonstrate the deadlock issue in cache store. -func TestIteratorDeadlock(t *testing.T) { - mem := dbadapter.Store{DB: dbm.NewMemDB()} - store := cachekv.NewStore(mem) - // the channel buffer is 64 and received once, so put at least 66 elements. - for i := 0; i < 66; i++ { - store.Set([]byte(fmt.Sprintf("key%d", i)), []byte{1}) - } - it := store.Iterator(nil, nil) - defer it.Close() - store.Set([]byte("key20"), []byte{1}) - // it'll be blocked here with previous version, or enable lock on btree. - it2 := store.Iterator(nil, nil) - defer it2.Close() -} - -//------------------------------------------------------------------------------------------- -// do some random ops - -const ( - opSet = 0 - opSetRange = 1 - opDel = 2 - opDelRange = 3 - opWrite = 4 - - totalOps = 5 // number of possible operations -) - -func randInt(n int) int { - return tmrand.NewRand().Int() % n -} - -// useful for replaying a error case if we find one -func doOp(t *testing.T, st types.CacheKVStore, truth dbm.DB, op int, args ...int) { - switch op { - case opSet: - k := args[0] - st.Set(keyFmt(k), valFmt(k)) - err := truth.Set(keyFmt(k), valFmt(k)) - require.NoError(t, err) - case opSetRange: - start := args[0] - end := args[1] - setRange(t, st, truth, start, end) - case opDel: - k := args[0] - st.Delete(keyFmt(k)) - err := truth.Delete(keyFmt(k)) - require.NoError(t, err) - case opDelRange: - start := args[0] - end := args[1] - deleteRange(t, st, truth, start, end) - case opWrite: - st.Write() - } -} - -func doRandomOp(t *testing.T, st types.CacheKVStore, truth dbm.DB, maxKey int) { - r := randInt(totalOps) - switch r { - case opSet: - k := randInt(maxKey) - st.Set(keyFmt(k), valFmt(k)) - err := truth.Set(keyFmt(k), valFmt(k)) - require.NoError(t, err) - case opSetRange: - start := randInt(maxKey - 2) - end := randInt(maxKey-start) + start - setRange(t, st, truth, start, end) - case opDel: - k := randInt(maxKey) - st.Delete(keyFmt(k)) - err := truth.Delete(keyFmt(k)) - require.NoError(t, err) - case opDelRange: - start := randInt(maxKey - 2) - end := randInt(maxKey-start) + start - deleteRange(t, st, truth, start, end) - case opWrite: - st.Write() - } -} - -//------------------------------------------------------------------------------------------- - -// iterate over whole domain -func assertIterateDomain(t *testing.T, st types.KVStore, expectedN int) { - itr := st.Iterator(nil, nil) - i := 0 - for ; itr.Valid(); itr.Next() { - k, v := itr.Key(), itr.Value() - require.Equal(t, keyFmt(i), k) - require.Equal(t, valFmt(i), v) - i++ - } - require.Equal(t, expectedN, i) - require.NoError(t, itr.Close()) -} - -func assertIterateDomainCheck(t *testing.T, st types.KVStore, mem dbm.DB, r []keyRange) { - // iterate over each and check they match the other - itr := st.Iterator(nil, nil) - itr2, err := mem.Iterator(nil, nil) // ground truth - require.NoError(t, err) - - krc := newKeyRangeCounter(r) - i := 0 - - for ; krc.valid(); krc.next() { - require.True(t, itr.Valid()) - require.True(t, itr2.Valid()) - - // check the key/val matches the ground truth - k, v := itr.Key(), itr.Value() - k2, v2 := itr2.Key(), itr2.Value() - require.Equal(t, k, k2) - require.Equal(t, v, v2) - - // check they match the counter - require.Equal(t, k, keyFmt(krc.key())) - - itr.Next() - itr2.Next() - i++ - } - - require.False(t, itr.Valid()) - require.False(t, itr2.Valid()) - require.NoError(t, itr.Close()) - require.NoError(t, itr2.Close()) -} - -func assertIterateDomainCompare(t *testing.T, st types.KVStore, mem dbm.DB) { - // iterate over each and check they match the other - itr := st.Iterator(nil, nil) - itr2, err := mem.Iterator(nil, nil) // ground truth - require.NoError(t, err) - checkIterators(t, itr, itr2) - checkIterators(t, itr2, itr) - require.NoError(t, itr.Close()) - require.NoError(t, itr2.Close()) -} - -func checkIterators(t *testing.T, itr, itr2 types.Iterator) { - for ; itr.Valid(); itr.Next() { - require.True(t, itr2.Valid()) - k, v := itr.Key(), itr.Value() - k2, v2 := itr2.Key(), itr2.Value() - require.Equal(t, k, k2) - require.Equal(t, v, v2) - itr2.Next() - } - require.False(t, itr.Valid()) - require.False(t, itr2.Valid()) -} - -//-------------------------------------------------------- - -func setRange(t *testing.T, st types.KVStore, mem dbm.DB, start, end int) { - for i := start; i < end; i++ { - st.Set(keyFmt(i), valFmt(i)) - err := mem.Set(keyFmt(i), valFmt(i)) - require.NoError(t, err) - } -} - -func deleteRange(t *testing.T, st types.KVStore, mem dbm.DB, start, end int) { - for i := start; i < end; i++ { - st.Delete(keyFmt(i)) - err := mem.Delete(keyFmt(i)) - require.NoError(t, err) - } -} - -//-------------------------------------------------------- - -type keyRange struct { - start int - end int -} - -func (kr keyRange) len() int { - return kr.end - kr.start -} - -func newKeyRangeCounter(kr []keyRange) *keyRangeCounter { - return &keyRangeCounter{keyRanges: kr} -} - -// we can iterate over this and make sure our real iterators have all the right keys -type keyRangeCounter struct { - rangeIdx int - idx int - keyRanges []keyRange -} - -func (krc *keyRangeCounter) valid() bool { - maxRangeIdx := len(krc.keyRanges) - 1 - maxRange := krc.keyRanges[maxRangeIdx] - - // if we're not in the max range, we're valid - if krc.rangeIdx <= maxRangeIdx && - krc.idx < maxRange.len() { - return true - } - - return false -} - -func (krc *keyRangeCounter) next() { - thisKeyRange := krc.keyRanges[krc.rangeIdx] - if krc.idx == thisKeyRange.len()-1 { - krc.rangeIdx++ - krc.idx = 0 - } else { - krc.idx++ - } -} - -func (krc *keyRangeCounter) key() int { - thisKeyRange := krc.keyRanges[krc.rangeIdx] - return thisKeyRange.start + krc.idx -} - -//-------------------------------------------------------- - -func bz(s string) []byte { return []byte(s) } - -func BenchmarkCacheKVStoreGetNoKeyFound(b *testing.B) { - b.ReportAllocs() - st := newCacheKVStore() - b.ResetTimer() - // assumes b.N < 2**24 - for i := 0; i < b.N; i++ { - st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}) - } -} - -func BenchmarkCacheKVStoreGetKeyFound(b *testing.B) { - b.ReportAllocs() - st := newCacheKVStore() - for i := 0; i < b.N; i++ { - arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} - st.Set(arr, arr) - } - b.ResetTimer() - // assumes b.N < 2**24 - for i := 0; i < b.N; i++ { - st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}) - } -} - -//-------------------------------------------------------- - -func BenchmarkCacheKVStoreSetAndCommit(b *testing.B) { - mem := dbadapter.Store{DB: dbm.NewMemDB()} - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - store := cachekv.NewStore(mem) - store1 := store.Clone() - for j := 0; j < 10; j++ { - store1.Set(sdk.Uint64ToBigEndian(uint64(i+j)), []byte{byte(i)}) - } - store.Restore(store1) - store.Write() - } -} diff --git a/store/cachemulti/store.go b/store/cachemulti/store.go deleted file mode 100644 index 43f64da39f..0000000000 --- a/store/cachemulti/store.go +++ /dev/null @@ -1,191 +0,0 @@ -package cachemulti - -import ( - "fmt" - "io" - - "github.com/cosmos/cosmos-sdk/store/tracekv" - "github.com/cosmos/cosmos-sdk/store/types" - "github.com/evmos/ethermint/store/cachekv" -) - -// storeNameCtxKey is the TraceContext metadata key that identifies -// the store which emitted a given trace. -const storeNameCtxKey = "store_name" - -//---------------------------------------- -// Store - -// Store holds many branched stores. -// Implements MultiStore. -// NOTE: a Store (and MultiStores in general) should never expose the -// keys for the substores. -type Store struct { - stores map[types.StoreKey]*cachekv.Store - - traceWriter io.Writer - traceContext types.TraceContext -} - -var _ types.CacheMultiStore = Store{} - -// NewFromKVStore creates a new Store object from a mapping of store keys to -// CacheWrapper objects and a KVStore as the database. Each CacheWrapper store -// is a branched store. -func NewFromKVStore( - stores map[types.StoreKey]types.KVStore, - traceWriter io.Writer, traceContext types.TraceContext, -) Store { - cms := Store{ - stores: make(map[types.StoreKey]*cachekv.Store, len(stores)), - traceWriter: traceWriter, - traceContext: traceContext, - } - - for key, store := range stores { - if cms.TracingEnabled() { - tctx := cms.traceContext.Clone().Merge(types.TraceContext{ - storeNameCtxKey: key.Name(), - }) - - store = tracekv.NewStore(store, cms.traceWriter, tctx) - } - cms.stores[key] = cachekv.NewStore(store) - } - - return cms -} - -// NewStore creates a new Store object from parent rootmulti store, it branch out inner store of the specified keys. -func NewStore( - parent types.MultiStore, keys map[string]*types.KVStoreKey, -) Store { - stores := make(map[types.StoreKey]types.KVStore, len(keys)) - for _, key := range keys { - stores[key] = parent.GetKVStore(key) - } - return NewFromKVStore(stores, nil, nil) -} - -func newCacheMultiStoreFromCMS(cms Store) Store { - stores := make(map[types.StoreKey]types.KVStore) - for k, v := range cms.stores { - stores[k] = v - } - - return NewFromKVStore(stores, cms.traceWriter, cms.traceContext) -} - -// SetTracer sets the tracer for the MultiStore that the underlying -// stores will utilize to trace operations. A MultiStore is returned. -func (cms Store) SetTracer(w io.Writer) types.MultiStore { - cms.traceWriter = w - return cms -} - -// SetTracingContext updates the tracing context for the MultiStore by merging -// the given context with the existing context by key. Any existing keys will -// be overwritten. It is implied that the caller should update the context when -// necessary between tracing operations. It returns a modified MultiStore. -func (cms Store) SetTracingContext(tc types.TraceContext) types.MultiStore { - if cms.traceContext != nil { - for k, v := range tc { - cms.traceContext[k] = v - } - } else { - cms.traceContext = tc - } - - return cms -} - -// TracingEnabled returns if tracing is enabled for the MultiStore. -func (cms Store) TracingEnabled() bool { - return cms.traceWriter != nil -} - -// LatestVersion returns the branch version of the store -func (cms Store) LatestVersion() int64 { - panic("cannot get latest version from branch cached multi-store") -} - -// GetStoreType returns the type of the store. -func (cms Store) GetStoreType() types.StoreType { - return types.StoreTypeMulti -} - -// Write calls Write on each underlying store. -func (cms Store) Write() { - for _, store := range cms.stores { - store.Write() - } -} - -// Clone creates a snapshot of each store of the cache-multistore. -// Each copy is a copy-on-write operation and therefore is very fast. -func (cms Store) Clone() types.CacheMultiStore { - stores := make(map[types.StoreKey]*cachekv.Store, len(cms.stores)) - for key, store := range cms.stores { - stores[key] = store.Clone() - } - return Store{ - stores: stores, - - traceWriter: cms.traceWriter, - traceContext: cms.traceContext, - } -} - -// Restore restores the cache-multistore cache to a given snapshot. -func (cms Store) Restore(s types.CacheMultiStore) { - ms := s.(Store) - for key, store := range cms.stores { - otherStore, ok := ms.stores[key] - if !ok { - panic("Invariant violation: Restore should only be called on a store cloned from itself") - } - store.Restore(otherStore) - } -} - -// Implements CacheWrapper. -func (cms Store) CacheWrap() types.CacheWrap { - return cms.CacheMultiStore().(types.CacheWrap) -} - -// CacheWrapWithTrace implements the CacheWrapper interface. -func (cms Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap { - return cms.CacheWrap() -} - -// Implements MultiStore. -func (cms Store) CacheMultiStore() types.CacheMultiStore { - return newCacheMultiStoreFromCMS(cms) -} - -// CacheMultiStoreWithVersion implements the MultiStore interface. It will panic -// as an already cached multi-store cannot load previous versions. -// -// TODO: The store implementation can possibly be modified to support this as it -// seems safe to load previous versions (heights). -func (cms Store) CacheMultiStoreWithVersion(_ int64) (types.CacheMultiStore, error) { - panic("cannot branch cached multi-store with a version") -} - -// GetStore returns an underlying Store by key. -func (cms Store) GetStore(key types.StoreKey) types.Store { - s := cms.stores[key] - if key == nil || s == nil { - panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) - } - return types.Store(s) -} - -// GetKVStore returns an underlying KVStore by key. -func (cms Store) GetKVStore(key types.StoreKey) types.KVStore { - store := cms.stores[key] - if key == nil || store == nil { - panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) - } - return types.KVStore(store) -} diff --git a/store/cachemulti/store_test.go b/store/cachemulti/store_test.go deleted file mode 100644 index 80d54a1f43..0000000000 --- a/store/cachemulti/store_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package cachemulti - -import ( - "fmt" - "testing" - - "github.com/evmos/ethermint/store/cachekv" - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/store/types" -) - -func TestStoreGetKVStore(t *testing.T) { - require := require.New(t) - - s := Store{stores: map[types.StoreKey]*cachekv.Store{}} - key := types.NewKVStoreKey("abc") - errMsg := fmt.Sprintf("kv store with key %v has not been registered in stores", key) - - require.PanicsWithValue(errMsg, - func() { s.GetStore(key) }) - - require.PanicsWithValue(errMsg, - func() { s.GetKVStore(key) }) -} diff --git a/tests/importer/importer_test.go b/tests/importer/importer_test.go index 24c69d83d6..a32a730206 100644 --- a/tests/importer/importer_test.go +++ b/tests/importer/importer_test.go @@ -140,7 +140,7 @@ func (suite *ImporterTestSuite) TestImportBlocks() { }) ctx := suite.app.NewContext(false, tmheader) ctx = ctx.WithBlockHeight(tmheader.Height) - vmdb := statedb.New(ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) + vmdb := statedb.New(ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 { applyDAOHardFork(vmdb) diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index b14f9658c8..a554c6ee6a 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -182,7 +182,7 @@ func (suite *EvmTestSuite) SignTx(tx *types.MsgEthereumTx) { } func (suite *EvmTestSuite) StateDB() *statedb.StateDB { - return statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) + return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) } func TestEvmTestSuite(t *testing.T) { diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index 2cb670170b..a56b78ff48 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -392,7 +392,7 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() { suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { suite.SetupTest() // reset - vmdb := statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()), txHash, txIndex, logIndex)) + vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()), txHash, txIndex, logIndex)) tc.malleate(vmdb) suite.Require().NoError(vmdb.Commit()) diff --git a/x/evm/keeper/hooks_test.go b/x/evm/keeper/hooks_test.go index 4c01bf2d71..b635cf4cd4 100644 --- a/x/evm/keeper/hooks_test.go +++ b/x/evm/keeper/hooks_test.go @@ -66,7 +66,7 @@ func (suite *KeeperTestSuite) TestEvmHooks() { k := suite.app.EvmKeeper ctx := suite.ctx txHash := common.BigToHash(big.NewInt(1)) - vmdb := statedb.New(ctx, suite.app.GetKeys(), k, statedb.NewTxConfig( + vmdb := statedb.New(ctx, k, statedb.NewTxConfig( common.BytesToHash(ctx.HeaderHash().Bytes()), txHash, 0, diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 6760a2ac14..eba0aa5b50 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -71,10 +71,6 @@ type Keeper struct { // Legacy subspace ss paramstypes.Subspace - - // a set of store keys that should cover all the precompile use cases, - // or ideally just pass the application's all stores. - keys map[string]*storetypes.KVStoreKey } // NewKeeper generates new evm module keeper @@ -88,7 +84,6 @@ func NewKeeper( fmk types.FeeMarketKeeper, tracer string, ss paramstypes.Subspace, - keys map[string]*storetypes.KVStoreKey, ) *Keeper { // ensure evm module account is set if addr := ak.GetModuleAddress(types.ModuleName); addr == nil { @@ -112,7 +107,6 @@ func NewKeeper( transientKey: transientKey, tracer: tracer, ss: ss, - keys: keys, } } diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 0acd382a8e..1b89527735 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -253,7 +253,7 @@ func (suite *KeeperTestSuite) Commit() { } func (suite *KeeperTestSuite) StateDB() *statedb.StateDB { - return statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) + return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) } // DeployTestContract deploy a test erc20 contract and returns the contract address diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 4e61bd16a0..d274cc648b 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -351,7 +351,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, return nil, errorsmod.Wrap(types.ErrCallDisabled, "failed to call contract") } - stateDB := statedb.New(ctx, k.keys, k, txConfig) + stateDB := statedb.New(ctx, k, txConfig) evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB) leftoverGas := msg.Gas() // Allow the tracer captures the tx level events, mainly the gas consumption. diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index 1bf3c45f46..b12d0dd460 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -690,7 +690,7 @@ func (suite *KeeperTestSuite) TestAddLog() { for _, tc := range testCases { suite.Run(tc.name, func() { suite.SetupTest() - vmdb := statedb.New(suite.ctx, suite.app.GetKeys(), suite.app.EvmKeeper, statedb.NewTxConfig( + vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig( common.BytesToHash(suite.ctx.HeaderHash().Bytes()), tc.hash, 0, 0, diff --git a/x/evm/statedb/interfaces.go b/x/evm/statedb/interfaces.go index 0b1dcc7905..2f1a119aa2 100644 --- a/x/evm/statedb/interfaces.go +++ b/x/evm/statedb/interfaces.go @@ -18,15 +18,8 @@ package statedb import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" ) -// ExtStateDB defines an extension to the interface provided by the go-ethereum -// codebase to support additional state transition functionalities. -type ExtStateDB interface { - vm.StateDB -} - // Keeper provide underlying storage of StateDB type Keeper interface { // Read methods diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 526105d855..3db1dff636 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -21,13 +21,11 @@ import ( "sort" errorsmod "cosmossdk.io/errors" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/evmos/ethermint/store/cachemulti" ) // revision is the identifier of a version of state. @@ -46,9 +44,8 @@ var _ vm.StateDB = &StateDB{} // * Contracts // * Accounts type StateDB struct { - keeper Keeper - ctx sdk.Context - cacheCtx sdk.Context + keeper Keeper + ctx sdk.Context // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. @@ -70,14 +67,11 @@ type StateDB struct { accessList *accessList } -// New creates a new state from a given trie, StateDB will branch out stores for the specified keys to support -// precompiles. -func New(ctx sdk.Context, keys map[string]*storetypes.KVStoreKey, keeper Keeper, txConfig TxConfig) *StateDB { - cacheCtx := ctx.WithMultiStore(cachemulti.NewStore(ctx.MultiStore(), keys)) +// New creates a new state from a given trie. +func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { return &StateDB{ keeper: keeper, ctx: ctx, - cacheCtx: cacheCtx, stateObjects: make(map[common.Address]*stateObject), journal: newJournal(), accessList: newAccessList(), @@ -86,13 +80,6 @@ func New(ctx sdk.Context, keys map[string]*storetypes.KVStoreKey, keeper Keeper, } } -// CacheMultiStore cast the multistore to *cachemulti.Store. -// invariant: the multistore must be a `cachemulti.Store`, -// prove: it's set in constructor and only modified in `restoreNativeState` which keeps the invariant. -func (s *StateDB) CacheMultiStore() cachemulti.Store { - return s.cacheCtx.MultiStore().(cachemulti.Store) -} - // Keeper returns the underlying `Keeper` func (s *StateDB) Keeper() Keeper { return s.keeper @@ -319,8 +306,9 @@ func (s *StateDB) restoreNativeState(ms sdk.MultiStore) { // the writes will be revert when either the native action itself fail // or the wrapping message call reverted. func (s *StateDB) ExecuteNativeAction(action func(ctx sdk.Context) error) error { - snapshot := s.CacheMultiStore().Clone() - err := action(s.cacheCtx) + snapshot := s.ctx + s.ctx, write := s.ctx.CacheContext() + err := action(s.ctx) if err != nil { s.restoreNativeState(snapshot) return err @@ -482,10 +470,6 @@ func (s *StateDB) RevertToSnapshot(revid int) { // Commit writes the dirty states to keeper // the StateDB object should be discarded after committed. func (s *StateDB) Commit() error { - // commit the native cache store first, - // the states managed by precompiles and the other part of StateDB must not overlap. - s.CacheMultiStore().Write() - for _, addr := range s.journal.sortedDirties() { obj := s.stateObjects[addr] if obj.suicided { diff --git a/x/evm/statedb/statedb_test.go b/x/evm/statedb/statedb_test.go index 586d49315c..3a491aa8cc 100644 --- a/x/evm/statedb/statedb_test.go +++ b/x/evm/statedb/statedb_test.go @@ -52,7 +52,7 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().Empty(acct.states) suite.Require().False(acct.account.IsContract()) - db = statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) + db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) suite.Require().Equal(true, db.Exist(address)) suite.Require().Equal(true, db.Empty(address)) suite.Require().Equal(big.NewInt(0), db.GetBalance(address)) @@ -74,7 +74,7 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().NoError(db.Commit()) // suicide - db = statedb.New(sdk.Context{}, nil, db.Keeper(), emptyTxConfig) + db = statedb.New(sdk.Context{}, db.Keeper(), emptyTxConfig) suite.Require().False(db.HasSuicided(address)) suite.Require().True(db.Suicide(address)) @@ -89,7 +89,7 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().NoError(db.Commit()) // not accessible from StateDB anymore - db = statedb.New(sdk.Context{}, nil, db.Keeper(), emptyTxConfig) + db = statedb.New(sdk.Context{}, db.Keeper(), emptyTxConfig) suite.Require().False(db.Exist(address)) // and cleared in keeper too @@ -101,7 +101,7 @@ func (suite *StateDBTestSuite) TestAccount() { for _, tc := range testCases { suite.Run(tc.name, func() { keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(db) }) } @@ -109,7 +109,7 @@ func (suite *StateDBTestSuite) TestAccount() { func (suite *StateDBTestSuite) TestAccountOverride() { keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) // test balance carry over when overwritten amount := big.NewInt(1) @@ -140,7 +140,7 @@ func (suite *StateDBTestSuite) TestDBError() { }}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) tc.malleate(db) suite.Require().Error(db.Commit()) } @@ -173,7 +173,7 @@ func (suite *StateDBTestSuite) TestBalance() { for _, tc := range testCases { suite.Run(tc.name, func() { keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(db) // check dirty state @@ -225,7 +225,7 @@ func (suite *StateDBTestSuite) TestState() { for _, tc := range testCases { suite.Run(tc.name, func() { keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(db) suite.Require().NoError(db.Commit()) @@ -233,7 +233,7 @@ func (suite *StateDBTestSuite) TestState() { suite.Require().Equal(tc.expStates, keeper.accounts[address].states) // check ForEachStorage - db = statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) + db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) collected := CollectContractStorage(db) if len(tc.expStates) > 0 { suite.Require().Equal(tc.expStates, collected) @@ -266,7 +266,7 @@ func (suite *StateDBTestSuite) TestCode() { for _, tc := range testCases { suite.Run(tc.name, func() { keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(db) // check dirty state @@ -277,7 +277,7 @@ func (suite *StateDBTestSuite) TestCode() { suite.Require().NoError(db.Commit()) // check again - db = statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) + db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) suite.Require().Equal(tc.expCode, db.GetCode(address)) suite.Require().Equal(len(tc.expCode), db.GetCodeSize(address)) suite.Require().Equal(tc.expCodeHash, db.GetCodeHash(address)) @@ -335,7 +335,7 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { { // do some arbitrary changes to the storage - db := statedb.New(ctx, nil, keeper, emptyTxConfig) + db := statedb.New(ctx, keeper, emptyTxConfig) db.SetNonce(address, 1) db.AddBalance(address, big.NewInt(100)) db.SetCode(address, []byte("hello world")) @@ -347,7 +347,7 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { originalKeeper := keeper.Clone() // run test - db := statedb.New(ctx, nil, keeper, emptyTxConfig) + db := statedb.New(ctx, keeper, emptyTxConfig) rev := db.Snapshot() tc.malleate(db) db.RevertToSnapshot(rev) @@ -369,7 +369,7 @@ func (suite *StateDBTestSuite) TestNestedSnapshot() { value1 := common.BigToHash(big.NewInt(1)) value2 := common.BigToHash(big.NewInt(2)) - db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) rev1 := db.Snapshot() db.SetState(address, key, value1) @@ -386,7 +386,7 @@ func (suite *StateDBTestSuite) TestNestedSnapshot() { } func (suite *StateDBTestSuite) TestInvalidSnapshotId() { - db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) suite.Require().Panics(func() { db.RevertToSnapshot(1) }) @@ -458,7 +458,7 @@ func (suite *StateDBTestSuite) TestAccessList() { } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) tc.malleate(db) } } @@ -471,7 +471,7 @@ func (suite *StateDBTestSuite) TestLog() { txHash, 1, 1, ) - db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), txConfig) + db := statedb.New(sdk.Context{}, NewMockKeeper(), txConfig) data := []byte("hello world") db.AddLog(ðtypes.Log{ Address: address, @@ -523,7 +523,7 @@ func (suite *StateDBTestSuite) TestRefund() { }, 0, true}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, nil, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) if !tc.expPanic { tc.malleate(db) suite.Require().Equal(tc.expRefund, db.GetRefund()) @@ -542,7 +542,7 @@ func (suite *StateDBTestSuite) TestIterateStorage() { value2 := common.BigToHash(big.NewInt(4)) keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, nil, keeper, emptyTxConfig) + db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) db.SetState(address, key1, value1) db.SetState(address, key2, value2) From d0451c776d29d3eaa61afc112d03e2bdaab59a45 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Thu, 13 Apr 2023 22:07:23 +0800 Subject: [PATCH 39/41] fix writeCache --- x/evm/statedb/native.go | 4 ++-- x/evm/statedb/statedb.go | 24 +++++++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/x/evm/statedb/native.go b/x/evm/statedb/native.go index 0178984624..e454452d49 100644 --- a/x/evm/statedb/native.go +++ b/x/evm/statedb/native.go @@ -1,14 +1,14 @@ package statedb import ( - "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" ) var _ JournalEntry = nativeChange{} type nativeChange struct { - snapshot types.MultiStore + snapshot sdk.CacheMultiStore } func (native nativeChange) Dirtied() *common.Address { diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 3db1dff636..6579fdac1c 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -44,8 +44,10 @@ var _ vm.StateDB = &StateDB{} // * Contracts // * Accounts type StateDB struct { - keeper Keeper - ctx sdk.Context + keeper Keeper + ctx sdk.Context + cacheCtx sdk.Context + writeCache func() // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. @@ -69,9 +71,12 @@ type StateDB struct { // New creates a new state from a given trie. func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { + cacheCtx, writeCache := ctx.CacheContext() return &StateDB{ keeper: keeper, ctx: ctx, + cacheCtx: cacheCtx, + writeCache: writeCache, stateObjects: make(map[common.Address]*stateObject), journal: newJournal(), accessList: newAccessList(), @@ -298,17 +303,21 @@ func (s *StateDB) setStateObject(object *stateObject) { s.stateObjects[object.Address()] = object } -func (s *StateDB) restoreNativeState(ms sdk.MultiStore) { - s.cacheCtx = s.cacheCtx.WithMultiStore(ms) +func (s *StateDB) restoreNativeState(cms sdk.CacheMultiStore) { + manager := sdk.NewEventManager() + s.cacheCtx = s.cacheCtx.WithMultiStore(cms).WithEventManager(manager) + s.writeCache = func() { + manager.EmitEvents(manager.Events()) + cms.Write() + } } // ExecuteNativeAction executes native action in isolate, // the writes will be revert when either the native action itself fail // or the wrapping message call reverted. func (s *StateDB) ExecuteNativeAction(action func(ctx sdk.Context) error) error { - snapshot := s.ctx - s.ctx, write := s.ctx.CacheContext() - err := action(s.ctx) + snapshot := s.ctx.MultiStore().CacheMultiStore() + err := action(s.cacheCtx) if err != nil { s.restoreNativeState(snapshot) return err @@ -470,6 +479,7 @@ func (s *StateDB) RevertToSnapshot(revid int) { // Commit writes the dirty states to keeper // the StateDB object should be discarded after committed. func (s *StateDB) Commit() error { + s.writeCache() for _, addr := range s.journal.sortedDirties() { obj := s.stateObjects[addr] if obj.suicided { From aa147e6217e9abd3d4fab45caa9206c5887e56fd Mon Sep 17 00:00:00 2001 From: mmsqe Date: Thu, 13 Apr 2023 22:19:18 +0800 Subject: [PATCH 40/41] check nil for writeCache and ms --- x/evm/statedb/statedb.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index 6579fdac1c..4e6c16298b 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -71,18 +71,19 @@ type StateDB struct { // New creates a new state from a given trie. func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { - cacheCtx, writeCache := ctx.CacheContext() - return &StateDB{ + db := &StateDB{ keeper: keeper, ctx: ctx, - cacheCtx: cacheCtx, - writeCache: writeCache, stateObjects: make(map[common.Address]*stateObject), journal: newJournal(), accessList: newAccessList(), txConfig: txConfig, } + if ctx.MultiStore() != nil { + db.cacheCtx, db.writeCache = ctx.CacheContext() + } + return db } // Keeper returns the underlying `Keeper` @@ -479,7 +480,9 @@ func (s *StateDB) RevertToSnapshot(revid int) { // Commit writes the dirty states to keeper // the StateDB object should be discarded after committed. func (s *StateDB) Commit() error { - s.writeCache() + if s.writeCache != nil { + s.writeCache() + } for _, addr := range s.journal.sortedDirties() { obj := s.stateObjects[addr] if obj.suicided { From 5377940127e82150c984e0deb156236fe40af41d Mon Sep 17 00:00:00 2001 From: mmsqe Date: Mon, 17 Apr 2023 10:19:40 +0800 Subject: [PATCH 41/41] add bank msg utility cmd replace transfer with raw --- client/bank.go | 15 ++++++ client/bank/msg_utility.go | 49 +++++++++++++++++++ cmd/ethermintd/root.go | 3 ++ tests/integration_tests/cosmoscli.py | 11 +++++ .../hardhat/contracts/TestBank.sol | 15 +++--- tests/integration_tests/test_precompiles.py | 9 ++-- x/evm/keeper/precompiles/bank.go | 43 ++++++---------- x/evm/types/interfaces.go | 33 +++++++++++-- 8 files changed, 135 insertions(+), 43 deletions(-) create mode 100644 client/bank.go create mode 100644 client/bank/msg_utility.go diff --git a/client/bank.go b/client/bank.go new file mode 100644 index 0000000000..2f4b0463cb --- /dev/null +++ b/client/bank.go @@ -0,0 +1,15 @@ +package client + +import ( + "github.com/evmos/ethermint/client/bank" + "github.com/spf13/cobra" +) + +func BankCommands() *cobra.Command { + cmd := &cobra.Command{ + Use: "bank", + Short: "Bank msg utility commands", + } + cmd.AddCommand(bank.MsgUtilityCommand()) + return cmd +} diff --git a/client/bank/msg_utility.go b/client/bank/msg_utility.go new file mode 100644 index 0000000000..8999092d98 --- /dev/null +++ b/client/bank/msg_utility.go @@ -0,0 +1,49 @@ +package bank + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/spf13/cobra" + + proto "github.com/gogo/protobuf/proto" + + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +func MsgUtilityCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "data ", + Short: "Generate encode data for msg send", + Long: `Generate encode data for msg send. +Example: + ethermintd bank data D09F7C8C4529CB5D387AA17E33D707C529A6F694 03EB2CBAE6754C6E459F444783D1557DCA0F4E1A 10evm/0x5003c1fcc043D2d81fF970266bf3fa6e8C5a1F3A + `, + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + coins, err := sdk.ParseCoinsNormalized(args[2]) + if err != nil { + return fmt.Errorf("failed to parse coins: %w", err) + } + msg := banktypes.NewMsgSend( + sdk.AccAddress(common.FromHex(args[0])), + sdk.AccAddress(common.FromHex(args[1])), + coins, + ) + bytes, err := proto.Marshal(msg) + if err != nil { + return err + } + + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + return clientCtx.PrintString(string(bytes)) + }, + } + + return cmd +} diff --git a/cmd/ethermintd/root.go b/cmd/ethermintd/root.go index 5474ac57d2..574c08fe44 100644 --- a/cmd/ethermintd/root.go +++ b/cmd/ethermintd/root.go @@ -148,6 +148,9 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { // add rosetta rootCmd.AddCommand(sdkserver.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Codec)) + // add bank utility commands + rootCmd.AddCommand(ethermintclient.BankCommands()) + return rootCmd, encodingConfig } diff --git a/tests/integration_tests/cosmoscli.py b/tests/integration_tests/cosmoscli.py index 41c24c91e7..7081c0f36a 100644 --- a/tests/integration_tests/cosmoscli.py +++ b/tests/integration_tests/cosmoscli.py @@ -839,3 +839,14 @@ def rollback(self): def migrate_keystore(self): return self.raw("keys", "migrate", home=self.data_dir) + + def encode_bank_msg(self, sender, recipient, coin: str, **kwargs): + return self.raw( + "bank", + "data", + sender, + recipient, + coin, + home=self.data_dir, + **kwargs, + ) diff --git a/tests/integration_tests/hardhat/contracts/TestBank.sol b/tests/integration_tests/hardhat/contracts/TestBank.sol index 6435dbac16..3e3532f5de 100644 --- a/tests/integration_tests/hardhat/contracts/TestBank.sol +++ b/tests/integration_tests/hardhat/contracts/TestBank.sol @@ -73,22 +73,19 @@ contract TestBank is ERC20 { moveToNativeStatic(amount); revert("test"); } - function encodeTransfer(address recipient, uint256 amount) internal view returns (bytes memory) { - return abi.encodeWithSignature("transfer(address,address,uint256)", msg.sender, recipient, amount); - } - function nativeTransfer(address recipient, uint256 amount) public { + function nativeTransfer(address recipient, uint256 amount, bytes memory data) public { _transfer(msg.sender, recipient, amount); - (bool result, ) = bankContract.call(encodeTransfer(recipient, amount)); + (bool result, ) = bankContract.call(abi.encodeWithSignature("transfer(bytes)", data)); require(result, "native transfer"); } - function nativeTransferDelegate(address recipient, uint256 amount) public { + function nativeTransferDelegate(address recipient, uint256 amount, bytes memory data) public { _transfer(msg.sender, recipient, amount); - (bool result, ) = bankContract.delegatecall(encodeTransfer(recipient, amount)); + (bool result, ) = bankContract.delegatecall(abi.encodeWithSignature("transfer(bytes)", data)); require(result, "native transfer"); } - function nativeTransferStatic(address recipient, uint256 amount) public { + function nativeTransferStatic(address recipient, uint256 amount, bytes memory data) public { _transfer(msg.sender, recipient, amount); - (bool result, ) = bankContract.staticcall(encodeTransfer(recipient, amount)); + (bool result, ) = bankContract.staticcall(abi.encodeWithSignature("transfer(bytes)", data)); require(result, "native transfer"); } } \ No newline at end of file diff --git a/tests/integration_tests/test_precompiles.py b/tests/integration_tests/test_precompiles.py index 9f2717f950..bb0fd9c94a 100644 --- a/tests/integration_tests/test_precompiles.py +++ b/tests/integration_tests/test_precompiles.py @@ -55,7 +55,8 @@ def assert_balance(tx, expect_status, amt): # test transfer amt3 = 10 addr2 = ADDRS["signer2"] - tx = contract.functions.nativeTransfer(addr2, amt3).build_transaction(data) + raw = cli.encode_bank_msg(addr, addr2, f"{amt3}{denom}") + tx = contract.functions.nativeTransfer(addr2, amt3, raw).build_transaction(data) balance = get_balance(cli, addr, denom) assert balance == contract.caller.nativeBalanceOf(addr) crc20_balance = contract.caller.balanceOf(addr) @@ -80,8 +81,9 @@ def assert_balance(tx, expect_status, amt): # test transfer to blocked address recipient = module_address("evm") amt4 = 20 + raw = cli.encode_bank_msg(addr, recipient, f"{amt4}{denom}") with pytest.raises(web3.exceptions.ContractLogicError): - contract.functions.nativeTransfer(recipient, amt4).build_transaction(data) + contract.functions.nativeTransfer(recipient, amt4, raw).build_transaction(data) @pytest.mark.parametrize("suffix", ["Delegate", "Static"]) @@ -124,8 +126,9 @@ def get_balances(): amt3 = 10 addr2 = ADDRS["signer2"] native_transfer = contract.functions["nativeTransfer" + suffix] + raw = cli.encode_bank_msg(addr, addr2, f"{amt3}{denom}") with pytest.raises(web3.exceptions.ContractLogicError): - native_transfer(addr2, amt3).build_transaction(data) + native_transfer(addr2, amt3, raw).build_transaction(data) # balance no change assert balances == get_balances() diff --git a/x/evm/keeper/precompiles/bank.go b/x/evm/keeper/precompiles/bank.go index 6f6e781e11..fef0d6ecb1 100644 --- a/x/evm/keeper/precompiles/bank.go +++ b/x/evm/keeper/precompiles/bank.go @@ -8,10 +8,13 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" + "github.com/gogo/protobuf/proto" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/evmos/ethermint/x/evm/types" ) @@ -27,6 +30,7 @@ var ( func init() { addressType, _ := abi.NewType("address", "", nil) uint256Type, _ := abi.NewType("uint256", "", nil) + bytesType, _ := abi.NewType("bytes", "", nil) MintMethod = abi.NewMethod( "mint", "mint", abi.Function, "", false, false, abi.Arguments{abi.Argument{ Name: "recipient", @@ -62,14 +66,8 @@ func init() { ) TransferMethod = abi.NewMethod( "transfer", "transfer", abi.Function, "", false, false, abi.Arguments{abi.Argument{ - Name: "sender", - Type: addressType, - }, abi.Argument{ - Name: "recipient", - Type: addressType, - }, abi.Argument{ - Name: "amount", - Type: uint256Type, + Name: "data", + Type: bytesType, }}, nil, ) @@ -82,12 +80,14 @@ func EVMDenom(token common.Address) string { type BankContract struct { ctx sdk.Context bankKeeper types.BankKeeper + msgServer banktypes.MsgServer stateDB ExtStateDB } // NewBankContract creates the precompiled contract to manage native tokens func NewBankContract(ctx sdk.Context, bankKeeper types.BankKeeper, stateDB ExtStateDB) StatefulPrecompiledContract { - return &BankContract{ctx, bankKeeper, stateDB} + msgServer := keeper.NewMsgServerImpl(bankKeeper) + return &BankContract{ctx, bankKeeper, msgServer, stateDB} } func (bc *BankContract) Address() common.Address { @@ -187,27 +187,14 @@ func (bc *BankContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ( if err != nil { return nil, errors.New("fail to unpack input arguments") } - sender := args[0].(common.Address) - recipient := args[1].(common.Address) - amount := args[2].(*big.Int) - if amount.Sign() <= 0 { - return nil, errors.New("invalid amount") - } - from := sdk.AccAddress(sender.Bytes()) - to := sdk.AccAddress(recipient.Bytes()) - if err := bc.checkBlockedAddr(to); err != nil { - return nil, err + bytes := args[0].([]byte) + var msgSend banktypes.MsgSend + if err = proto.Unmarshal(bytes, &msgSend); err != nil { + return nil, errors.New("fail to Unmarshal input arguments") } - denom := EVMDenom(contract.CallerAddress) - amt := sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(amount)) err = bc.stateDB.ExecuteNativeAction(func(ctx sdk.Context) error { - if err := bc.bankKeeper.IsSendEnabledCoins(ctx, amt); err != nil { - return err - } - if err := bc.bankKeeper.SendCoins(ctx, from, to, sdk.NewCoins(amt)); err != nil { - return errorsmod.Wrap(err, "fail to send coins in precompiled contract") - } - return nil + _, err = bc.msgServer.Send(ctx, &msgSend) + return err }) return nil, err default: diff --git a/x/evm/types/interfaces.go b/x/evm/types/interfaces.go index 8253dd22cd..032c2262ad 100644 --- a/x/evm/types/interfaces.go +++ b/x/evm/types/interfaces.go @@ -18,12 +18,13 @@ package types import ( "math/big" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" @@ -51,6 +52,32 @@ type BankKeeper interface { BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error BlockedAddr(addr sdk.AccAddress) bool + banktypes.QueryServer + DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error + DelegateCoinsFromAccountToModule( + ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins, + ) error + InitGenesis(sdk.Context, *banktypes.GenesisState) + ExportGenesis(sdk.Context) *banktypes.GenesisState + bankkeeper.ViewKeeper + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + GetPaginatedTotalSupply(ctx sdk.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error) + GetParams(ctx sdk.Context) banktypes.Params + GetSupply(ctx sdk.Context, denom string) sdk.Coin + HasDenomMetaData(ctx sdk.Context, denom string) bool + HasSupply(ctx sdk.Context, denom string) bool + InputOutputCoins(ctx sdk.Context, inputs []banktypes.Input, outputs []banktypes.Output) error + IsSendEnabledCoin(ctx sdk.Context, coin sdk.Coin) bool + IterateAllDenomMetaData(ctx sdk.Context, cb func(banktypes.Metadata) bool) + IterateTotalSupply(ctx sdk.Context, cb func(sdk.Coin) bool) + SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error + SetDenomMetaData(ctx sdk.Context, denomMetaData banktypes.Metadata) + SetParams(ctx sdk.Context, params banktypes.Params) + UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error + UndelegateCoinsFromModuleToAccount( + ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, + ) error + WithMintCoinsRestriction(check bankkeeper.MintingRestrictionFn) bankkeeper.BaseKeeper } // StakingKeeper returns the historical headers kept in store.