diff --git a/.github/workflows/forge-test.yml b/.github/workflows/forge-test.yml index f104ac3b5..d3ee84877 100644 --- a/.github/workflows/forge-test.yml +++ b/.github/workflows/forge-test.yml @@ -31,10 +31,10 @@ jobs: - name: Run Forge build run: | forge --version - forge build --sizes + forge build --sizes --evm-version=cancun id: build - name: Run Forge tests run: | - forge test -vvv + forge test -vvv --evm-version=cancun id: test diff --git a/CHANGELOG.md b/CHANGELOG.md index c27641bfb..578d175c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,23 @@ Ref: https://keepachangelog.com/en/1.0.0/ --> # Changelog +## v5.7.1 & v5.7.2 +sei-chain +* [#1779](https://github.com/sei-protocol/sei-chain/pull/1779) Fix subscribe logs empty params crash +* [#1783](https://github.com/sei-protocol/sei-chain/pull/1783) Add meaningful message for eth_call balance override overflow +* [#1783](https://github.com/sei-protocol/sei-chain/pull/1784) Fix log index on synthetic receipt +* [#1775](https://github.com/sei-protocol/sei-chain/pull/1775) Disallow sending to direct cast addr after association + +sei-wasmd +* [60](https://github.com/sei-protocol/sei-wasmd/pull/60) Query penalty fixes + +sei-tendermint +* [#237](https://github.com/sei-protocol/sei-tendermint/pull/237) Add metrics for total txs bytes in mempool + +## v5.7.0 +sei-chain +* [#1731](https://github.com/sei-protocol/sei-chain/pull/1731) Remove 1-hop limit +* [#1663](https://github.com/sei-protocol/sei-chain/pull/1663) Retain pointer address on upgrade ## v5.6.0 sei-chain diff --git a/app/ante_test.go b/app/ante_test.go index 7d2a7544c..a433e5d83 100644 --- a/app/ante_test.go +++ b/app/ante_test.go @@ -262,7 +262,7 @@ func TestEvmAnteErrorHandler(t *testing.T) { Log: "nonce too high", }}) testkeeper.EVMTestApp.EvmKeeper.SetMsgs([]*evmtypes.MsgEVMTransaction{req}) - deferredInfo := testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + deferredInfo := testkeeper.EVMTestApp.EvmKeeper.GetAllEVMTxDeferredInfo(ctx) require.Equal(t, 1, len(deferredInfo)) require.Contains(t, deferredInfo[0].Error, "nonce too high") } diff --git a/app/app.go b/app/app.go index d12e2c25b..9d2caa80d 100644 --- a/app/app.go +++ b/app/app.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "math" "math/big" "net/http" "os" @@ -46,9 +47,11 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + genesistypes "github.com/cosmos/cosmos-sdk/types/genesis" aclmodule "github.com/cosmos/cosmos-sdk/x/accesscontrol" aclclient "github.com/cosmos/cosmos-sdk/x/accesscontrol/client" aclconstants "github.com/cosmos/cosmos-sdk/x/accesscontrol/constants" @@ -394,6 +397,8 @@ type App struct { evmTracer *tracing.Hooks lightInvarianceConfig LightInvarianceConfig + genesisImportConfig genesistypes.GenesisImportConfig + receiptStore seidb.StateStore } @@ -634,6 +639,7 @@ func New( tkeys[evmtypes.TransientStoreKey], app.GetSubspace(evmtypes.ModuleName), app.receiptStore, app.BankKeeper, &app.AccountKeeper, &app.StakingKeeper, app.TransferKeeper, wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper), &app.WasmKeeper) + app.BankKeeper.RegisterRecipientChecker(app.EvmKeeper.CanAddressReceive) bApp.SetPreCommitHandler(app.HandlePreCommit) bApp.SetCloseHandler(app.HandleClose) @@ -670,6 +676,12 @@ func New( } app.lightInvarianceConfig = lightInvarianceConfig + genesisImportConfig, err := ReadGenesisImportConfig(appOpts) + if err != nil { + panic(fmt.Sprintf("error reading genesis import config due to %s", err)) + } + app.genesisImportConfig = genesisImportConfig + customDependencyGenerators := aclmapping.NewCustomDependencyGenerator() aclOpts = append(aclOpts, aclkeeper.WithResourceTypeToStoreKeyMap(aclutils.ResourceTypeToStoreKeyMap)) aclOpts = append(aclOpts, aclkeeper.WithDependencyGeneratorMappings(customDependencyGenerators.GetCustomDependencyGenerators(app.EvmKeeper))) @@ -724,6 +736,7 @@ func New( wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper), app.WasmKeeper, stakingkeeper.NewMsgServerImpl(app.StakingKeeper), + stakingkeeper.Querier{Keeper: app.StakingKeeper}, app.GovKeeper, app.DistrKeeper, app.OracleKeeper, @@ -1006,6 +1019,8 @@ func New( app.HardForkManager = upgrades.NewHardForkManager(app.ChainID) app.HardForkManager.RegisterHandler(v0upgrade.NewHardForkUpgradeHandler(100_000, upgrades.ChainIDSeiHardForkTest, app.WasmKeeper)) + app.RegisterDeliverTxHook(app.AddCosmosEventsToEVMReceiptIfApplicable) + if app.evmRPCConfig.LiveEVMTracer != "" { chainConfig := evmtypes.DefaultChainConfig().EthereumConfig(app.EvmKeeper.ChainID(app.GetCheckCtx())) evmTracer, err := evmtracers.NewBlockchainTracer(evmtracers.GlobalLiveTracerRegistry, app.evmRPCConfig.LiveEVMTracer, chainConfig) @@ -1120,12 +1135,14 @@ func (app *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.Respo // InitChainer application update at chain initialization func (app *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { var genesisState GenesisState - if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { - panic(err) + if !app.genesisImportConfig.StreamGenesisImport { + if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + panic(err) + } } ctx = ctx.WithContext(app.decorateContextWithDexMemState(ctx.Context())) app.UpgradeKeeper.SetModuleVersionMap(ctx, app.mm.GetVersionMap()) - return app.mm.InitGenesis(ctx, app.appCodec, genesisState) + return app.mm.InitGenesis(ctx, app.appCodec, genesisState, app.genesisImportConfig) } func (app *App) PrepareProposalHandler(_ sdk.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) { @@ -1498,6 +1515,39 @@ func (app *App) ExecuteTxsConcurrently(ctx sdk.Context, txs [][]byte, typedTxs [ return results, ctx } +func (app *App) GetDeliverTxEntry(ctx sdk.Context, txIndex int, absoluateIndex int, bz []byte, tx sdk.Tx) (res *sdk.DeliverTxEntry) { + var txTracer sdk.TxTracer + if app.evmTracer != nil { + txTracer = app.evmTracer + if app.evmTracer.GetTxTracer != nil { + txTracer = app.evmTracer.GetTxTracer(absoluateIndex) + } + } + + res = &sdk.DeliverTxEntry{ + Request: abci.RequestDeliverTx{Tx: bz}, + SdkTx: tx, + Checksum: sha256.Sum256(bz), + AbsoluteIndex: absoluateIndex, + TxTracer: txTracer, + } + if tx == nil { + return + } + defer func() { + if err := recover(); err != nil { + ctx.Logger().Error(fmt.Sprintf("panic when generating estimated writeset for %X: %s", bz, err)) + } + }() + // get prefill estimate + estimatedWritesets, err := app.AccessControlKeeper.GenerateEstimatedWritesets(ctx, app.GetAnteDepGenerator(), txIndex, tx) + // if no error, then we assign the mapped writesets for prefill estimate + if err == nil { + res.EstimatedWritesets = estimatedWritesets + } + return +} + // ProcessTXsWithOCC runs the transactions concurrently via OCC func (app *App) ProcessTXsWithOCC(ctx sdk.Context, txs [][]byte, typedTxs []sdk.Tx, absoluteTxIndices []int) ([]*abci.ExecTxResult, sdk.Context) { entries := make([]*sdk.DeliverTxEntry, len(txs)) @@ -1510,30 +1560,7 @@ func (app *App) ProcessTXsWithOCC(ctx sdk.Context, txs [][]byte, typedTxs []sdk. wg.Add(1) go func(txIndex int, tx []byte) { defer wg.Done() - - var txTracer sdk.TxTracer - if app.evmTracer != nil { - txTracer = app.evmTracer - if app.evmTracer.GetTxTracer != nil { - txTracer = app.evmTracer.GetTxTracer(absoluteTxIndices[txIndex]) - } - } - - deliverTxEntry := &sdk.DeliverTxEntry{ - Request: abci.RequestDeliverTx{Tx: tx}, - SdkTx: typedTxs[txIndex], - Checksum: sha256.Sum256(tx), - AbsoluteIndex: absoluteTxIndices[txIndex], - TxTracer: txTracer, - } - - // get prefill estimate - estimatedWritesets, err := app.AccessControlKeeper.GenerateEstimatedWritesets(ctx, app.GetAnteDepGenerator(), txIndex, typedTxs[txIndex]) - // if no error, then we assign the mapped writesets for prefill estimate - if err == nil { - deliverTxEntry.EstimatedWritesets = estimatedWritesets - } - entries[txIndex] = deliverTxEntry + entries[txIndex] = app.GetDeliverTxEntry(ctx, txIndex, absoluteTxIndices[txIndex], tx, typedTxs[txIndex]) }(txIndex, tx) } @@ -1589,7 +1616,6 @@ func (app *App) BuildDependenciesAndRunTxs(ctx sdk.Context, txs [][]byte, typedT func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequest, lastCommit abci.CommitInfo) (events []abci.Event, txResults []*abci.ExecTxResult, endBlockResp abci.ResponseEndBlock, err error) { ctx = ctx.WithIsOCCEnabled(app.OccEnabled()) - goCtx := app.decorateContextWithDexMemState(ctx.Context()) ctx = ctx.WithContext(goCtx) @@ -1639,11 +1665,7 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ prioritizedResults, ctx := app.ExecuteTxsConcurrently(ctx, prioritizedTxs, prioritizedTypedTxs, prioritizedIndices) for relativePrioritizedIndex, originalIndex := range prioritizedIndices { txResults[originalIndex] = prioritizedResults[relativePrioritizedIndex] - if emsg := evmtypes.GetEVMTransactionMessage(prioritizedTypedTxs[relativePrioritizedIndex]); emsg != nil && !emsg.IsAssociateTx() { - evmTxs[originalIndex] = emsg - } else { - evmTxs[originalIndex] = nil - } + evmTxs[originalIndex] = app.GetEVMMsg(prioritizedTypedTxs[relativePrioritizedIndex]) } // Finalize all Bank Module Transfers here so that events are included for prioritiezd txs @@ -1656,11 +1678,7 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ otherResults, ctx := app.ExecuteTxsConcurrently(ctx, otherTxs, otherTypedTxs, otherIndices) for relativeOtherIndex, originalIndex := range otherIndices { txResults[originalIndex] = otherResults[relativeOtherIndex] - if emsg := evmtypes.GetEVMTransactionMessage(otherTypedTxs[relativeOtherIndex]); emsg != nil && !emsg.IsAssociateTx() { - evmTxs[originalIndex] = emsg - } else { - evmTxs[originalIndex] = nil - } + evmTxs[originalIndex] = app.GetEVMMsg(otherTypedTxs[relativeOtherIndex]) } app.EvmKeeper.SetTxResults(txResults) app.EvmKeeper.SetMsgs(evmTxs) @@ -1677,6 +1695,21 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ return events, txResults, endBlockResp, nil } +func (app *App) GetEVMMsg(tx sdk.Tx) (res *evmtypes.MsgEVMTransaction) { + defer func() { + if err := recover(); err != nil { + res = nil + } + }() + if tx == nil { + return nil + } else if emsg := evmtypes.GetEVMTransactionMessage(tx); emsg != nil && !emsg.IsAssociateTx() { + return emsg + } else { + return nil + } +} + func (app *App) DecodeTransactionsConcurrently(ctx sdk.Context, txs [][]byte) []sdk.Tx { typedTxs := make([]sdk.Tx, len(txs)) wg := sync.WaitGroup{} @@ -1684,6 +1717,12 @@ func (app *App) DecodeTransactionsConcurrently(ctx sdk.Context, txs [][]byte) [] wg.Add(1) go func(idx int, encodedTx []byte) { defer wg.Done() + defer func() { + if err := recover(); err != nil { + ctx.Logger().Error(fmt.Sprintf("encountered panic during transaction decoding: %s", err)) + typedTxs[idx] = nil + } + }() typedTx, err := app.txDecoder(encodedTx) // get txkey from tx if err != nil { @@ -1923,7 +1962,13 @@ func (app *App) checkTotalBlockGasWanted(ctx sdk.Context, txs [][]byte) bool { if isGasless { continue } - totalGasWanted += feeTx.GetGas() + // Check for overflow before adding + gasWanted := feeTx.GetGas() + if int64(gasWanted) < 0 || int64(totalGasWanted) > math.MaxInt64-int64(gasWanted) { + return false + } + + totalGasWanted += gasWanted if totalGasWanted > uint64(ctx.ConsensusParams().Block.MaxGas) { // early return return false @@ -1985,6 +2030,11 @@ func (app *App) BlacklistedAccAddrs() map[string]bool { return blacklistedAddrs } +// test-only +func (app *App) SetTxDecoder(txDecoder sdk.TxDecoder) { + app.txDecoder = txDecoder +} + func (app *App) decorateProcessProposalContextWithDexMemState(base context.Context) context.Context { return context.WithValue(base, dexutils.DexMemStateContextKey, app.ProcessProposalMemState) } diff --git a/app/app_test.go b/app/app_test.go index ad689e27d..b28160188 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -4,15 +4,17 @@ import ( "context" "encoding/hex" "fmt" + "math" + "math/big" + "reflect" + "testing" + "time" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server/api" cosmosConfig "github.com/cosmos/cosmos-sdk/server/config" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" - "math/big" - "reflect" - "testing" - "time" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" @@ -360,6 +362,33 @@ func TestInvalidProposalWithExcessiveGasWanted(t *testing.T) { require.Equal(t, abci.ResponseProcessProposal_REJECT, res.Status) } +func TestOverflowGas(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + + testWrapper := app.NewTestWrapper(t, tm, valPub, false) + ap := testWrapper.App + ctx := testWrapper.Ctx.WithConsensusParams(&types.ConsensusParams{ + Block: &types.BlockParams{MaxGas: math.MaxInt64}, + }) + emptyTxBuilder := app.MakeEncodingConfig().TxConfig.NewTxBuilder() + txEncoder := app.MakeEncodingConfig().TxConfig.TxEncoder() + emptyTxBuilder.SetGasLimit(uint64(math.MaxInt64)) + emptyTx, _ := txEncoder(emptyTxBuilder.GetTx()) + + secondEmptyTxBuilder := app.MakeEncodingConfig().TxConfig.NewTxBuilder() + secondEmptyTxBuilder.SetGasLimit(10) + secondTx, _ := txEncoder(secondEmptyTxBuilder.GetTx()) + + proposal := abci.RequestProcessProposal{ + Txs: [][]byte{emptyTx, secondTx}, + Height: 1, + } + res, err := ap.ProcessProposalHandler(ctx, &proposal) + require.Nil(t, err) + require.Equal(t, abci.ResponseProcessProposal_REJECT, res.Status) +} + func TestDecodeTransactionsConcurrently(t *testing.T) { tm := time.Now().UTC() valPub := secp256k1.GenPrivKey().PubKey() @@ -410,6 +439,13 @@ func TestDecodeTransactionsConcurrently(t *testing.T) { require.NotNil(t, typedTxs[0].GetMsgs()[0].(*evmtypes.MsgEVMTransaction).Derived) require.Nil(t, typedTxs[1]) require.NotNil(t, typedTxs[2]) + + // test panic handling + testWrapper.App.SetTxDecoder(func(txBytes []byte) (sdk.Tx, error) { panic("test") }) + typedTxs = testWrapper.App.DecodeTransactionsConcurrently(testWrapper.Ctx, [][]byte{evmtxbz, invalidbz, banktxbz}) + require.Nil(t, typedTxs[0]) + require.Nil(t, typedTxs[1]) + require.Nil(t, typedTxs[2]) } func TestApp_RegisterAPIRoutes(t *testing.T) { @@ -464,6 +500,52 @@ func TestApp_RegisterAPIRoutes(t *testing.T) { } } +func TestGetEVMMsg(t *testing.T) { + a := &app.App{} + require.Nil(t, a.GetEVMMsg(nil)) + require.Nil(t, a.GetEVMMsg(app.MakeEncodingConfig().TxConfig.NewTxBuilder().GetTx())) + tb := app.MakeEncodingConfig().TxConfig.NewTxBuilder() + tb.SetMsgs(&evmtypes.MsgEVMTransaction{}) // invalid msg + require.Nil(t, a.GetEVMMsg(tb.GetTx())) + + tb = app.MakeEncodingConfig().TxConfig.NewTxBuilder() + privKey := testkeeper.MockPrivateKey() + testPrivHex := hex.EncodeToString(privKey.Bytes()) + key, _ := crypto.HexToECDSA(testPrivHex) + txData := ethtypes.LegacyTx{} + chainCfg := evmtypes.DefaultChainConfig() + ethCfg := chainCfg.EthereumConfig(big.NewInt(config.DefaultChainID)) + signer := ethtypes.MakeSigner(ethCfg, big.NewInt(1), uint64(123)) + tx, err := ethtypes.SignTx(ethtypes.NewTx(&txData), signer, key) + ethtxdata, _ := ethtx.NewTxDataFromTx(tx) + if err != nil { + return + } + msg, _ := evmtypes.NewMsgEVMTransaction(ethtxdata) + tb.SetMsgs(msg) + require.NotNil(t, a.GetEVMMsg(tb.GetTx())) +} + +func TestGetDeliverTxEntry(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + + testWrapper := app.NewTestWrapper(t, tm, valPub, false) + ap := testWrapper.App + ctx := testWrapper.Ctx.WithConsensusParams(&types.ConsensusParams{ + Block: &types.BlockParams{MaxGas: 10}, + }) + emptyTxBuilder := app.MakeEncodingConfig().TxConfig.NewTxBuilder() + txEncoder := app.MakeEncodingConfig().TxConfig.TxEncoder() + emptyTxBuilder.SetGasLimit(10) + tx := emptyTxBuilder.GetTx() + bz, _ := txEncoder(tx) + + require.NotNil(t, ap.GetDeliverTxEntry(ctx, 0, 0, bz, tx)) + + require.NotNil(t, ap.GetDeliverTxEntry(ctx, 0, 0, bz, nil)) +} + func isSwaggerRouteAdded(router *mux.Router) bool { var isAdded bool err := router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { diff --git a/app/export.go b/app/export.go index b2e33f633..6c6bfca48 100644 --- a/app/export.go +++ b/app/export.go @@ -2,7 +2,9 @@ package app import ( "encoding/json" + "fmt" "log" + "os" "github.com/cosmos/cosmos-sdk/types/kv" @@ -49,6 +51,41 @@ func (app *App) ExportAppStateAndValidators( }, nil } +func (app *App) ExportAppToFileStateAndValidators( + forZeroHeight bool, jailAllowedAddrs []string, file *os.File, +) (servertypes.ExportedApp, error) { + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + + // We export at last height + 1, because that's the height at which + // Tendermint will start InitChain. + height := app.LastBlockHeight() + 1 + if forZeroHeight { + height = 0 + app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) + } + err := app.mm.ProcessGenesisPerModule(ctx, app.appCodec, func(moduleName string, moduleJson json.RawMessage) error { + _, err := file.Write([]byte(fmt.Sprintf("{\"app_state\": {\"module\":\"%s\",\"data\":%s}}\n", moduleName, string(moduleJson)))) + if err != nil { + return err + } + return nil + }) + if err != nil { + return servertypes.ExportedApp{}, err + } + validators, err := staking.WriteValidators(ctx, app.StakingKeeper) + if err != nil { + return servertypes.ExportedApp{}, err + } + + return servertypes.ExportedApp{ + Validators: validators, + Height: height, + ConsensusParams: app.BaseApp.GetConsensusParams(ctx), + }, nil +} + // AddressFromValidatorsKey creates the validator operator address from ValidatorsKey func AddressFromValidatorsKey(key []byte) []byte { kv.AssertKeyAtLeastLength(key, 3) diff --git a/app/genesis.go b/app/genesis.go index 8e89ec8d4..489104839 100644 --- a/app/genesis.go +++ b/app/genesis.go @@ -4,11 +4,39 @@ import ( "encoding/json" "github.com/cosmos/cosmos-sdk/codec" + genesistypes "github.com/cosmos/cosmos-sdk/types/genesis" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" sdk "github.com/cosmos/cosmos-sdk/types" + + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/spf13/cast" +) + +var DefaultGenesisConfig = genesistypes.GenesisImportConfig{ + StreamGenesisImport: false, + GenesisStreamFile: "", +} + +const ( + flagGenesisStreamImport = "genesis.stream-import" + flagGenesisImportFile = "genesis.import-file" ) +func ReadGenesisImportConfig(opts servertypes.AppOptions) (genesistypes.GenesisImportConfig, error) { + cfg := DefaultGenesisConfig // copy + var err error + if v := opts.Get(flagGenesisStreamImport); v != nil { + if cfg.StreamGenesisImport, err = cast.ToBoolE(v); err != nil { + return cfg, err + } + } + if v := opts.Get(flagGenesisImportFile); v != nil { + cfg.GenesisStreamFile = v.(string) + } + return cfg, nil +} + // The genesis state of the blockchain is represented here as a map of raw json // messages key'd by a identifier string. // The identifier is used to determine which module genesis information belongs diff --git a/app/receipt.go b/app/receipt.go new file mode 100644 index 000000000..214f2ceff --- /dev/null +++ b/app/receipt.go @@ -0,0 +1,346 @@ +package app + +import ( + "encoding/json" + "fmt" + "math" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/sei-protocol/sei-chain/utils" + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" + evmtracers "github.com/sei-protocol/sei-chain/x/evm/tracers" + "github.com/sei-protocol/sei-chain/x/evm/tracing" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +const ShellEVMTxType = math.MaxUint32 + +var ERC20ApprovalTopic = common.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925") +var ERC20TransferTopic = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") +var ERC721TransferTopic = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") +var ERC721ApprovalTopic = common.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925") +var ERC721ApproveAllTopic = common.HexToHash("0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31") +var EmptyHash = common.HexToHash("0x0") +var TrueHash = common.HexToHash("0x1") + +type AllowanceResponse struct { + Allowance sdk.Int `json:"allowance"` + Expires json.RawMessage `json:"expires"` +} + +func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.Tx, checksum [32]byte, response abci.ResponseDeliverTx) { + if response.Code > 0 { + return + } + + wasmEvents := GetEventsOfType(response, wasmtypes.WasmModuleEventType) + logs := []*ethtypes.Log{} + for _, wasmEvent := range wasmEvents { + contractAddr, found := GetAttributeValue(wasmEvent, wasmtypes.AttributeKeyContractAddr) + if !found { + continue + } + // check if there is a ERC20 pointer to contractAddr + pointerAddr, _, exists := app.EvmKeeper.GetERC20CW20Pointer(ctx, contractAddr) + if exists { + log, eligible := app.translateCW20Event(ctx, wasmEvent, pointerAddr, contractAddr) + if eligible { + log.Index = uint(len(logs)) + logs = append(logs, log) + } + continue + } + // check if there is a ERC721 pointer to contract Addr + pointerAddr, _, exists = app.EvmKeeper.GetERC721CW721Pointer(ctx, contractAddr) + if exists { + log, eligible := app.translateCW721Event(ctx, wasmEvent, pointerAddr, contractAddr) + if eligible { + log.Index = uint(len(logs)) + logs = append(logs, log) + } + continue + } + } + if len(logs) == 0 { + return + } + + txHash := common.BytesToHash(checksum[:]) + if response.EvmTxInfo != nil { + txHash = common.HexToHash(response.EvmTxInfo.TxHash) + } + + addedLogs := utils.Map(logs, evmkeeper.ConvertSyntheticEthLog) + + var bloom ethtypes.Bloom + if receipt, err := app.EvmKeeper.GetTransientReceipt(ctx, txHash); err == nil && receipt != nil { + receipt.Logs = append(receipt.Logs, addedLogs...) + for i, l := range receipt.Logs { + l.Index = uint32(i) + } + bloom = ethtypes.CreateBloom(ethtypes.Receipts{ðtypes.Receipt{Logs: evmkeeper.GetLogsForTx(receipt)}}) + receipt.LogsBloom = bloom[:] + _ = app.EvmKeeper.SetTransientReceipt(ctx, txHash, receipt) + + if tracer := evmtracers.GetCtxBlockchainTracer(ctx); tracer != nil && tracer.OnSeiPostTxCosmosEvents != nil { + app.traceSeiPostTxCosmosEvents(ctx, tracer, tx, txHash, addedLogs, receipt, true) + } + } else { + bloom = ethtypes.CreateBloom(ethtypes.Receipts{ðtypes.Receipt{Logs: logs}}) + receipt = &evmtypes.Receipt{ + TxType: ShellEVMTxType, + TxHashHex: txHash.Hex(), + GasUsed: ctx.GasMeter().GasConsumed(), + BlockNumber: uint64(ctx.BlockHeight()), + TransactionIndex: uint32(ctx.TxIndex()), + Logs: addedLogs, + LogsBloom: bloom[:], + Status: uint32(ethtypes.ReceiptStatusSuccessful), // we don't create shell receipt for failed Cosmos tx since there is no event anyway + } + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if ok && len(sigTx.GetSigners()) > 0 { + // use the first signer as the `from` + receipt.From = app.EvmKeeper.GetEVMAddressOrDefault(ctx, sigTx.GetSigners()[0]).Hex() + } + _ = app.EvmKeeper.SetTransientReceipt(ctx, txHash, receipt) + + if tracer := evmtracers.GetCtxBlockchainTracer(ctx); tracer != nil && tracer.OnSeiPostTxCosmosEvents != nil { + app.traceSeiPostTxCosmosEvents(ctx, tracer, tx, txHash, addedLogs, receipt, false) + } + } + if d, found := app.EvmKeeper.GetEVMTxDeferredInfo(ctx); found { + app.EvmKeeper.AppendToEvmTxDeferredInfo(ctx, bloom, txHash, d.Surplus) + } else { + app.EvmKeeper.AppendToEvmTxDeferredInfo(ctx, bloom, txHash, sdk.ZeroInt()) + } +} + +func (app *App) traceSeiPostTxCosmosEvents( + ctx sdk.Context, + tracer *tracing.Hooks, + tx sdk.Tx, + txHash common.Hash, + addedLogs []*evmtypes.Log, + newReceipt *evmtypes.Receipt, + onEvmTransaction bool, +) { + tracer.OnSeiPostTxCosmosEvents(tracing.SeiPostTxCosmosEvent{ + TxHash: txHash, + Tx: tx, + AddedLogs: addedLogs, + NewReceipt: newReceipt, + OnEVMTransaction: onEvmTransaction, + EVMAddressOrDefault: func(address sdk.AccAddress) common.Address { + return app.EvmKeeper.GetEVMAddressOrDefault(ctx, address) + }, + }) +} + +func (app *App) translateCW20Event(ctx sdk.Context, wasmEvent abci.Event, pointerAddr common.Address, contractAddr string) (*ethtypes.Log, bool) { + action, found := GetAttributeValue(wasmEvent, "action") + if !found { + return nil, false + } + var topics []common.Hash + switch action { + case "mint", "burn", "send", "transfer", "transfer_from", "send_from", "burn_from": + topics = []common.Hash{ + ERC20TransferTopic, + app.GetEvmAddressAttribute(ctx, wasmEvent, "from"), + app.GetEvmAddressAttribute(ctx, wasmEvent, "to"), + } + amount, found := GetAmountAttribute(wasmEvent) + if !found { + return nil, false + } + return ðtypes.Log{ + Address: pointerAddr, + Topics: topics, + Data: common.BigToHash(amount).Bytes(), + }, true + case "increase_allowance", "decrease_allowance": + ownerStr, found := GetAttributeValue(wasmEvent, "owner") + if !found { + return nil, false + } + spenderStr, found := GetAttributeValue(wasmEvent, "spender") + if !found { + return nil, false + } + topics := []common.Hash{ + ERC20ApprovalTopic, + app.GetEvmAddressAttribute(ctx, wasmEvent, "owner"), + app.GetEvmAddressAttribute(ctx, wasmEvent, "spender"), + } + res, err := app.WasmKeeper.QuerySmart( + ctx, + sdk.MustAccAddressFromBech32(contractAddr), + []byte(fmt.Sprintf("{\"allowance\":{\"owner\":\"%s\",\"spender\":\"%s\"}}", ownerStr, spenderStr)), + ) + if err != nil { + return nil, false + } + allowanceResponse := &AllowanceResponse{} + if err := json.Unmarshal(res, allowanceResponse); err != nil { + return nil, false + } + return ðtypes.Log{ + Address: pointerAddr, + Topics: topics, + Data: common.BigToHash(allowanceResponse.Allowance.BigInt()).Bytes(), + }, true + } + return nil, false +} + +func (app *App) translateCW721Event(ctx sdk.Context, wasmEvent abci.Event, pointerAddr common.Address, contractAddr string) (*ethtypes.Log, bool) { + action, found := GetAttributeValue(wasmEvent, "action") + if !found { + return nil, false + } + var topics []common.Hash + switch action { + case "transfer_nft", "send_nft", "burn": + topics = []common.Hash{ + ERC721TransferTopic, + app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"), + app.GetEvmAddressAttribute(ctx, wasmEvent, "recipient"), + } + tokenID := GetTokenIDAttribute(wasmEvent) + if tokenID == nil { + return nil, false + } + return ðtypes.Log{ + Address: pointerAddr, + Topics: topics, + Data: common.BigToHash(tokenID).Bytes(), + }, true + case "mint": + topics = []common.Hash{ + ERC721TransferTopic, + EmptyHash, + app.GetEvmAddressAttribute(ctx, wasmEvent, "owner"), + } + tokenID := GetTokenIDAttribute(wasmEvent) + if tokenID == nil { + return nil, false + } + return ðtypes.Log{ + Address: pointerAddr, + Topics: topics, + Data: common.BigToHash(tokenID).Bytes(), + }, true + case "approve": + topics = []common.Hash{ + ERC721ApprovalTopic, + app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"), + app.GetEvmAddressAttribute(ctx, wasmEvent, "spender"), + } + tokenID := GetTokenIDAttribute(wasmEvent) + if tokenID == nil { + return nil, false + } + return ðtypes.Log{ + Address: pointerAddr, + Topics: topics, + Data: common.BigToHash(tokenID).Bytes(), + }, true + case "revoke": + topics = []common.Hash{ + ERC721ApprovalTopic, + app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"), + EmptyHash, + } + tokenID := GetTokenIDAttribute(wasmEvent) + if tokenID == nil { + return nil, false + } + return ðtypes.Log{ + Address: pointerAddr, + Topics: topics, + Data: common.BigToHash(tokenID).Bytes(), + }, true + case "approve_all": + topics = []common.Hash{ + ERC721ApproveAllTopic, + app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"), + app.GetEvmAddressAttribute(ctx, wasmEvent, "operator"), + } + return ðtypes.Log{ + Address: pointerAddr, + Topics: topics, + Data: TrueHash.Bytes(), + }, true + case "revoke_all": + topics = []common.Hash{ + ERC721ApproveAllTopic, + app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"), + app.GetEvmAddressAttribute(ctx, wasmEvent, "operator"), + } + return ðtypes.Log{ + Address: pointerAddr, + Topics: topics, + Data: EmptyHash.Bytes(), + }, true + } + return nil, false +} + +func (app *App) GetEvmAddressAttribute(ctx sdk.Context, event abci.Event, attribute string) common.Hash { + addrStr, found := GetAttributeValue(event, attribute) + if found { + seiAddr, err := sdk.AccAddressFromBech32(addrStr) + if err == nil { + evmAddr := app.EvmKeeper.GetEVMAddressOrDefault(ctx, seiAddr) + return common.BytesToHash(evmAddr[:]) + } + } + return EmptyHash +} + +func GetEventsOfType(rdtx abci.ResponseDeliverTx, ty string) (res []abci.Event) { + for _, event := range rdtx.Events { + if event.Type == ty { + res = append(res, event) + } + } + return +} + +func GetAttributeValue(event abci.Event, attribute string) (string, bool) { + for _, attr := range event.Attributes { + if string(attr.Key) == attribute { + return string(attr.Value), true + } + } + return "", false +} + +func GetAmountAttribute(event abci.Event) (*big.Int, bool) { + amount, found := GetAttributeValue(event, "amount") + if found { + amountInt, ok := sdk.NewIntFromString(amount) + if ok { + return amountInt.BigInt(), true + } + } + return nil, false +} + +func GetTokenIDAttribute(event abci.Event) *big.Int { + tokenID, found := GetAttributeValue(event, "token_id") + if !found { + return nil + } + tokenIDInt, ok := sdk.NewIntFromString(tokenID) + if !ok { + return nil + } + return tokenIDInt.BigInt() +} diff --git a/app/receipt_test.go b/app/receipt_test.go new file mode 100644 index 000000000..d1ce00052 --- /dev/null +++ b/app/receipt_test.go @@ -0,0 +1,399 @@ +package app_test + +import ( + "crypto/sha256" + "embed" + "encoding/hex" + "fmt" + "math/big" + "os" + "testing" + "time" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + "github.com/cosmos/cosmos-sdk/client" + clienttx "github.com/cosmos/cosmos-sdk/client/tx" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" + "github.com/sei-protocol/sei-chain/precompiles/wasmd" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/sei-protocol/sei-chain/x/evm/types/ethtx" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +//go:embed wasm_abi.json +var f embed.FS + +func TestEvmEventsForCw20(t *testing.T) { + k := testkeeper.EVMTestApp.EvmKeeper + wasmKeeper := k.WasmKeeper() + ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()).WithChainID("sei-test").WithBlockHeight(1) + code, err := os.ReadFile("../contracts/wasm/cw20_base.wasm") + require.Nil(t, err) + privKey := testkeeper.MockPrivateKey() + creator, _ := testkeeper.PrivateKeyToAddresses(privKey) + codeID, err := wasmKeeper.Create(ctx, creator, code, nil) + require.Nil(t, err) + contractAddr, _, err := wasmKeeper.Instantiate(ctx, codeID, creator, creator, []byte(fmt.Sprintf("{\"name\":\"test\",\"symbol\":\"test\",\"decimals\":6,\"initial_balances\":[{\"address\":\"%s\",\"amount\":\"1000000000\"}]}", creator.String())), "test", sdk.NewCoins()) + require.Nil(t, err) + + _, mockPointerAddr := testkeeper.MockAddressPair() + k.SetERC20CW20Pointer(ctx, contractAddr.String(), mockPointerAddr) + + // calling CW contract directly + amt := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000000000))) + k.BankKeeper().MintCoins(ctx, "evm", amt) + k.BankKeeper().SendCoinsFromModuleToAccount(ctx, "evm", creator, amt) + recipient, _ := testkeeper.MockAddressPair() + payload := []byte(fmt.Sprintf("{\"transfer\":{\"recipient\":\"%s\",\"amount\":\"100\"}}", recipient.String())) + msg := &wasmtypes.MsgExecuteContract{ + Sender: creator.String(), + Contract: contractAddr.String(), + Msg: payload, + } + txBuilder := testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) + txBuilder.SetGasLimit(300000) + tx := signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator)) + txbz, err := testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + sum := sha256.Sum256(txbz) + res := testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err := testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:])) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + _, found := testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + require.True(t, found) + + // calling from wasmd precompile + abi := pcommon.MustGetABI(f, "wasm_abi.json") + emptyCoins, err := sdk.NewCoins().MarshalJSON() + require.Nil(t, err) + data, err := abi.Pack("execute", contractAddr.String(), payload, emptyCoins) + require.Nil(t, err) + wasmAddr := common.HexToAddress(wasmd.WasmdAddress) + txData := ethtypes.LegacyTx{ + Nonce: 0, + GasPrice: big.NewInt(1000000000), + Gas: 1000000, + To: &wasmAddr, + Data: data, + } + chainID := k.ChainID(ctx) + chainCfg := evmtypes.DefaultChainConfig() + ethCfg := chainCfg.EthereumConfig(chainID) + blockNum := big.NewInt(ctx.BlockHeight()) + signer := ethtypes.MakeSigner(ethCfg, blockNum, uint64(ctx.BlockTime().Unix())) + testPrivHex := hex.EncodeToString(privKey.Bytes()) + key, _ := crypto.HexToECDSA(testPrivHex) + signedTx, err := ethtypes.SignTx(ethtypes.NewTx(&txData), signer, key) + require.Nil(t, err) + typedTx, err := ethtx.NewLegacyTx(signedTx) + require.Nil(t, err) + emsg, err := evmtypes.NewMsgEVMTransaction(typedTx) + require.Nil(t, err) + txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(emsg) + tx = txBuilder.GetTx() + txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()).WithTxIndex(1), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, signedTx.Hash()) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + _, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + require.True(t, found) + + // test approval message + payload = []byte(fmt.Sprintf("{\"increase_allowance\":{\"spender\":\"%s\",\"amount\":\"100\"}}", recipient.String())) + msg = &wasmtypes.MsgExecuteContract{ + Sender: creator.String(), + Contract: contractAddr.String(), + Msg: payload, + } + txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) + txBuilder.SetGasLimit(300000) + tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator)) + txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + sum = sha256.Sum256(txbz) + res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:])) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + _, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + require.True(t, found) + require.Equal(t, common.HexToHash("0x64").Bytes(), receipt.Logs[0].Data) +} + +func TestEvmEventsForCw721(t *testing.T) { + k := testkeeper.EVMTestApp.EvmKeeper + wasmKeeper := k.WasmKeeper() + ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()).WithChainID("sei-test").WithBlockHeight(1) + code, err := os.ReadFile("../contracts/wasm/cw721_base.wasm") + require.Nil(t, err) + privKey := testkeeper.MockPrivateKey() + creator, _ := testkeeper.PrivateKeyToAddresses(privKey) + codeID, err := wasmKeeper.Create(ctx, creator, code, nil) + require.Nil(t, err) + contractAddr, _, err := wasmKeeper.Instantiate(ctx, codeID, creator, creator, []byte(fmt.Sprintf("{\"name\":\"test\",\"symbol\":\"test\",\"minter\":\"%s\"}", creator.String())), "test", sdk.NewCoins()) + require.Nil(t, err) + + _, mockPointerAddr := testkeeper.MockAddressPair() + k.SetERC721CW721Pointer(ctx, contractAddr.String(), mockPointerAddr) + + // calling CW contract directly + amt := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000000000))) + k.BankKeeper().MintCoins(ctx, "evm", amt) + k.BankKeeper().SendCoinsFromModuleToAccount(ctx, "evm", creator, amt) + recipient, _ := testkeeper.MockAddressPair() + payload := []byte(fmt.Sprintf("{\"mint\":{\"token_id\":\"1\",\"owner\":\"%s\"}}", recipient.String())) + msg := &wasmtypes.MsgExecuteContract{ + Sender: creator.String(), + Contract: contractAddr.String(), + Msg: payload, + } + txBuilder := testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) + txBuilder.SetGasLimit(300000) + tx := signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator)) + txbz, err := testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + sum := sha256.Sum256(txbz) + res := testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err := testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:])) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + _, found := testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + require.True(t, found) + + // calling from wasmd precompile + abi := pcommon.MustGetABI(f, "wasm_abi.json") + emptyCoins, err := sdk.NewCoins().MarshalJSON() + require.Nil(t, err) + payload = []byte(fmt.Sprintf("{\"mint\":{\"token_id\":\"2\",\"owner\":\"%s\"}}", creator.String())) + data, err := abi.Pack("execute", contractAddr.String(), payload, emptyCoins) + require.Nil(t, err) + wasmAddr := common.HexToAddress(wasmd.WasmdAddress) + txData := ethtypes.LegacyTx{ + Nonce: 0, + GasPrice: big.NewInt(1000000000), + Gas: 1000000, + To: &wasmAddr, + Data: data, + } + chainID := k.ChainID(ctx) + chainCfg := evmtypes.DefaultChainConfig() + ethCfg := chainCfg.EthereumConfig(chainID) + blockNum := big.NewInt(ctx.BlockHeight()) + signer := ethtypes.MakeSigner(ethCfg, blockNum, uint64(ctx.BlockTime().Unix())) + testPrivHex := hex.EncodeToString(privKey.Bytes()) + key, _ := crypto.HexToECDSA(testPrivHex) + signedTx, err := ethtypes.SignTx(ethtypes.NewTx(&txData), signer, key) + require.Nil(t, err) + typedTx, err := ethtx.NewLegacyTx(signedTx) + require.Nil(t, err) + emsg, err := evmtypes.NewMsgEVMTransaction(typedTx) + require.Nil(t, err) + txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(emsg) + tx = txBuilder.GetTx() + txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()).WithTxIndex(1), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, signedTx.Hash()) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + _, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + require.True(t, found) + + // test approval message + payload = []byte(fmt.Sprintf("{\"approve\":{\"spender\":\"%s\",\"token_id\":\"2\"}}", recipient.String())) + msg = &wasmtypes.MsgExecuteContract{ + Sender: creator.String(), + Contract: contractAddr.String(), + Msg: payload, + } + txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) + txBuilder.SetGasLimit(300000) + tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator)) + txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + sum = sha256.Sum256(txbz) + res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:])) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + require.Equal(t, uint32(0), receipt.Logs[0].Index) + _, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + require.True(t, found) + require.Equal(t, common.HexToHash("0x2").Bytes(), receipt.Logs[0].Data) + + // revoke + payload = []byte(fmt.Sprintf("{\"revoke\":{\"spender\":\"%s\",\"token_id\":\"2\"}}", recipient.String())) + msg = &wasmtypes.MsgExecuteContract{ + Sender: creator.String(), + Contract: contractAddr.String(), + Msg: payload, + } + txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) + txBuilder.SetGasLimit(300000) + tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator)) + txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + sum = sha256.Sum256(txbz) + res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:])) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + _, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + require.True(t, found) + require.Equal(t, common.HexToHash("0x2").Bytes(), receipt.Logs[0].Data) + + // approve all + payload = []byte(fmt.Sprintf("{\"approve_all\":{\"operator\":\"%s\"}}", recipient.String())) + msg = &wasmtypes.MsgExecuteContract{ + Sender: creator.String(), + Contract: contractAddr.String(), + Msg: payload, + } + txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) + txBuilder.SetGasLimit(300000) + tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator)) + txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + sum = sha256.Sum256(txbz) + res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:])) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + _, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + require.True(t, found) + require.Equal(t, common.HexToHash("0x1").Bytes(), receipt.Logs[0].Data) + + // revoke all + payload = []byte(fmt.Sprintf("{\"revoke_all\":{\"operator\":\"%s\"}}", recipient.String())) + msg = &wasmtypes.MsgExecuteContract{ + Sender: creator.String(), + Contract: contractAddr.String(), + Msg: payload, + } + txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) + txBuilder.SetGasLimit(300000) + tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator)) + txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + sum = sha256.Sum256(txbz) + res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:])) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + _, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + require.True(t, found) + require.Equal(t, common.HexToHash("0x0").Bytes(), receipt.Logs[0].Data) + + // burn + payload = []byte("{\"burn\":{\"token_id\":\"2\"}}") + msg = &wasmtypes.MsgExecuteContract{ + Sender: creator.String(), + Contract: contractAddr.String(), + Msg: payload, + } + txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) + txBuilder.SetGasLimit(300000) + tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator)) + txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + sum = sha256.Sum256(txbz) + res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:])) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + _, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) + require.True(t, found) + require.Equal(t, common.HexToHash("0x2").Bytes(), receipt.Logs[0].Data) +} + +func signTx(txBuilder client.TxBuilder, privKey cryptotypes.PrivKey, acc authtypes.AccountI) sdk.Tx { + var sigsV2 []signing.SignatureV2 + sigV2 := signing.SignatureV2{ + PubKey: privKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: testkeeper.EVMTestApp.GetTxConfig().SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: acc.GetSequence(), + } + sigsV2 = append(sigsV2, sigV2) + _ = txBuilder.SetSignatures(sigsV2...) + sigsV2 = []signing.SignatureV2{} + signerData := xauthsigning.SignerData{ + ChainID: "sei-test", + AccountNumber: acc.GetAccountNumber(), + Sequence: acc.GetSequence(), + } + sigV2, _ = clienttx.SignWithPrivKey( + testkeeper.EVMTestApp.GetTxConfig().SignModeHandler().DefaultMode(), + signerData, + txBuilder, + privKey, + testkeeper.EVMTestApp.GetTxConfig(), + acc.GetSequence(), + ) + sigsV2 = append(sigsV2, sigV2) + _ = txBuilder.SetSignatures(sigsV2...) + return txBuilder.GetTx() +} diff --git a/app/test_state_store.go b/app/test_state_store.go index 520c06c0f..a942221a2 100644 --- a/app/test_state_store.go +++ b/app/test_state_store.go @@ -2,6 +2,7 @@ package app import ( "errors" + "sort" "sync" seidbproto "github.com/sei-protocol/sei-db/proto" @@ -257,6 +258,7 @@ func NewInMemoryIterator(data map[string][]byte, start, end []byte) *InMemoryIte keys = append(keys, k) } } + sort.Strings(keys) return &InMemoryIterator{ data: data, diff --git a/app/upgrades.go b/app/upgrades.go index d37374184..14233f12f 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -104,6 +104,11 @@ var upgradesList = []string{ "v5.5.5", "v5.6.0", "v5.6.2", + "v5.7.0", + "v5.7.1", + "v5.7.2", + "v5.7.4", + "v5.7.5", } // if there is an override list, use that instead, for integration tests diff --git a/app/wasm_abi.json b/app/wasm_abi.json new file mode 100644 index 000000000..c5e5b1b3f --- /dev/null +++ b/app/wasm_abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/cmd/seid/cmd/root.go b/cmd/seid/cmd/root.go index f8f5a51f6..df8ed1fe1 100644 --- a/cmd/seid/cmd/root.go +++ b/cmd/seid/cmd/root.go @@ -313,7 +313,33 @@ func appExport( forZeroHeight bool, jailAllowedAddrs []string, appOpts servertypes.AppOptions, + file *os.File, ) (servertypes.ExportedApp, error) { + exportableApp, err := getExportableApp( + logger, + db, + traceStore, + height, + appOpts, + ) + if err != nil { + return servertypes.ExportedApp{}, err + } + + if file == nil { + return exportableApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs) + } else { + return exportableApp.ExportAppToFileStateAndValidators(forZeroHeight, jailAllowedAddrs, file) + } +} + +func getExportableApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + height int64, + appOpts servertypes.AppOptions, +) (*app.App, error) { encCfg := app.MakeEncodingConfig() encCfg.Marshaler = codec.NewProtoCodec(encCfg.InterfaceRegistry) @@ -321,19 +347,19 @@ func appExport( homePath, ok := appOpts.Get(flags.FlagHome).(string) if !ok || homePath == "" { - return servertypes.ExportedApp{}, errors.New("application home not set") + return nil, errors.New("application home not set") } if height != -1 { exportableApp = app.New(logger, db, traceStore, false, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), true, nil, encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts, app.EmptyACLOpts, app.EmptyAppOptions) if err := exportableApp.LoadHeight(height); err != nil { - return servertypes.ExportedApp{}, err + return nil, err } } else { exportableApp = app.New(logger, db, traceStore, true, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), true, nil, encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts, app.EmptyACLOpts, app.EmptyAppOptions) } + return exportableApp, nil - return exportableApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs) } func getPrimeNums(lo int, hi int) []int { diff --git a/contracts/hardhat.config.js b/contracts/hardhat.config.js index 9bf1ab2b5..3088f6320 100644 --- a/contracts/hardhat.config.js +++ b/contracts/hardhat.config.js @@ -5,8 +5,9 @@ require('@openzeppelin/hardhat-upgrades'); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: { - version: "0.8.20", + version: "0.8.25", settings: { + evmVersion: "cancun", optimizer: { enabled: true, runs: 1000, diff --git a/contracts/src/CW20ERC20Pointer.sol b/contracts/src/CW20ERC20Pointer.sol index 9568ba32d..b5cdc516f 100644 --- a/contracts/src/CW20ERC20Pointer.sol +++ b/contracts/src/CW20ERC20Pointer.sol @@ -73,7 +73,6 @@ contract CW20ERC20Pointer is ERC20 { string memory req = _curlyBrace(_formatPayload("increase_allowance", _curlyBrace(_join(spenderAddr, amt, ",")))); _execute(bytes(req)); } - emit Approval(msg.sender, spender, amount); return true; } @@ -83,7 +82,6 @@ contract CW20ERC20Pointer is ERC20 { string memory amt = _formatPayload("amount", _doubleQuotes(Strings.toString(amount))); string memory req = _curlyBrace(_formatPayload("transfer", _curlyBrace(_join(recipient, amt, ",")))); _execute(bytes(req)); - emit Transfer(msg.sender, to, amount); return true; } @@ -94,7 +92,6 @@ contract CW20ERC20Pointer is ERC20 { string memory amt = _formatPayload("amount", _doubleQuotes(Strings.toString(amount))); string memory req = _curlyBrace(_formatPayload("transfer_from", _curlyBrace(_join(_join(sender, recipient, ","), amt, ",")))); _execute(bytes(req)); - emit Transfer(from, to, amount); return true; } diff --git a/contracts/src/CW721ERC721Pointer.sol b/contracts/src/CW721ERC721Pointer.sol index 3898b69e3..7eea1a390 100644 --- a/contracts/src/CW721ERC721Pointer.sol +++ b/contracts/src/CW721ERC721Pointer.sol @@ -158,7 +158,6 @@ contract CW721ERC721Pointer is ERC721,ERC2981 { string memory tId = _formatPayload("token_id", _doubleQuotes(Strings.toString(tokenId))); string memory req = _curlyBrace(_formatPayload("transfer_nft", _curlyBrace(_join(recipient, tId, ",")))); _execute(bytes(req)); - emit Transfer(from, to, tokenId); } function approve(address approved, uint256 tokenId) public override { @@ -166,7 +165,6 @@ contract CW721ERC721Pointer is ERC721,ERC2981 { string memory tId = _formatPayload("token_id", _doubleQuotes(Strings.toString(tokenId))); string memory req = _curlyBrace(_formatPayload("approve", _curlyBrace(_join(spender, tId, ",")))); _execute(bytes(req)); - emit Approval(ownerOf(tokenId), approved, tokenId); } function setApprovalForAll(address operator, bool approved) public override { @@ -176,7 +174,6 @@ contract CW721ERC721Pointer is ERC721,ERC2981 { } else { _execute(bytes(_curlyBrace(_formatPayload("revoke_all", op)))); } - emit ApprovalForAll(msg.sender, operator, approved); } function _execute(bytes memory req) internal returns (bytes memory) { diff --git a/contracts/src/ERC20PreTransferFromWrapper.sol b/contracts/src/ERC20PreTransferFromWrapper.sol new file mode 100644 index 000000000..89d422331 --- /dev/null +++ b/contracts/src/ERC20PreTransferFromWrapper.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +// This contract permits testing the case where where app add cowasm logs to and +// existing EVM transaction that emitted a log before the post tx hook is invoked. +contract ERC20PreTransferFromWrapper { + IERC20 public wrapped; + + event PreTransferFrom( + address indexed from, + address indexed to, + uint256 amount + ); + + constructor(address wrapped_) { + wrapped = IERC20(wrapped_); + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public returns (bool) { + emit PreTransferFrom(from, to, amount); + require(wrapped.transferFrom(from, to, amount), "Transfer from failed"); + return true; + } +} diff --git a/contracts/src/EVMCompatibilityTester.sol b/contracts/src/EVMCompatibilityTester.sol index 0331ffc9f..6c2ef8385 100644 --- a/contracts/src/EVMCompatibilityTester.sol +++ b/contracts/src/EVMCompatibilityTester.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.25; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; @@ -173,5 +173,12 @@ contract EVMCompatibilityTester { bytesVar = value; emit BytesSet(msg.sender, value); } + + function getBlobBaseFee() public view returns (uint256 fee) { + assembly { + fee := blobbasefee() + } + return fee; + } } diff --git a/contracts/test/AssociateTest.js b/contracts/test/AssociateTest.js index 04e35eda1..40c4af735 100644 --- a/contracts/test/AssociateTest.js +++ b/contracts/test/AssociateTest.js @@ -14,7 +14,8 @@ describe("Associate Balances", function () { }, "test3": { seiAddress: 'sei1qkawqt7dw09rkvn53lm2deamtfcpuq9v0h6zur', - evmAddress: '0xCb2FB25A6a34Ca874171Ac0406d05A49BC45a1cF' + evmAddress: '0xCb2FB25A6a34Ca874171Ac0406d05A49BC45a1cF', + castAddress: 'sei1evhmykn2xn9gwst34szqd5z6fx7ytgw0l7g0vs', } } @@ -81,7 +82,12 @@ describe("Associate Balances", function () { await verifyAssociation(addr.seiAddress, addr.evmAddress, async function(){ await associateKey("test3") return BigInt(0) - }) + }); + + // it should not be able to send funds to the cast address after association + expect(await getSeiBalance(addr.castAddress)).to.equal(0); + await fundSeiAddress(addr.castAddress, "100"); + expect(await getSeiBalance(addr.castAddress)).to.equal(0); }); }) \ No newline at end of file diff --git a/contracts/test/CW20toERC20PointerTest.js b/contracts/test/CW20toERC20PointerTest.js index 4d8154406..8c695260e 100644 --- a/contracts/test/CW20toERC20PointerTest.js +++ b/contracts/test/CW20toERC20PointerTest.js @@ -98,7 +98,19 @@ describe("CW20 to ERC20 Pointer", function () { const respBefore = await queryWasm(pointer, "balance", {address: accounts[1].seiAddress}); const balanceBefore = respBefore.data.balance; - await executeWasm(pointer, { transfer: { recipient: accounts[1].seiAddress, amount: "100" } }); + const res = await executeWasm(pointer, { transfer: { recipient: accounts[1].seiAddress, amount: "100" } }); + const txHash = res["txhash"]; + const receipt = await ethers.provider.getTransactionReceipt(`0x${txHash}`); + expect(receipt).not.to.be.null; + const filter = { + fromBlock: receipt["blockNumber"], + toBlock: 'latest', + address: receipt["to"], + topics: [ethers.id("Transfer(address,address,uint256)")] + }; + const logs = await ethers.provider.getLogs(filter); + expect(logs.length).to.equal(1); + expect(logs[0]["topics"][0]).to.equal(ethers.id("Transfer(address,address,uint256)")); const respAfter = await queryWasm(pointer, "balance", {address: accounts[1].seiAddress}); const balanceAfter = respAfter.data.balance; diff --git a/contracts/test/CW721ERC721PointerTest.t.sol b/contracts/test/CW721ERC721PointerTest.t.sol index 59baad9f8..cb2ce4e58 100644 --- a/contracts/test/CW721ERC721PointerTest.t.sol +++ b/contracts/test/CW721ERC721PointerTest.t.sol @@ -238,8 +238,8 @@ contract CW721ERC721PointerTest is Test { abi.encode(bytes("")) ); vm.startPrank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - vm.expectEmit(); - emit Transfer(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, 1); + //vm.expectEmit(); + //emit Transfer(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, 1); pointer.transferFrom(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, 1); vm.stopPrank(); } @@ -261,8 +261,8 @@ contract CW721ERC721PointerTest is Test { abi.encode(bytes("sei19zhelek4q5lt4zam8mcarmgv92vzgqd3ux32jw")) ); vm.startPrank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - vm.expectEmit(); - emit Approval(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, 1); + //vm.expectEmit(); + //emit Approval(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, 1); pointer.approve(0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, 1); vm.stopPrank(); } @@ -274,8 +274,8 @@ contract CW721ERC721PointerTest is Test { abi.encode(bytes("")) ); vm.startPrank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - vm.expectEmit(); - emit ApprovalForAll(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, true); + //vm.expectEmit(); + //emit ApprovalForAll(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, true); pointer.setApprovalForAll(0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, true); vm.stopPrank(); } @@ -287,8 +287,8 @@ contract CW721ERC721PointerTest is Test { abi.encode(bytes("")) ); vm.startPrank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - vm.expectEmit(); - emit ApprovalForAll(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, false); + //vm.expectEmit(); + //emit ApprovalForAll(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, false); pointer.setApprovalForAll(0xF39fD6e51Aad88F6f4CE6AB8827279CFffb92267, false); vm.stopPrank(); } diff --git a/contracts/test/ERC20toCW20PointerTest.js b/contracts/test/ERC20toCW20PointerTest.js index f2bf02a1b..860cb879b 100644 --- a/contracts/test/ERC20toCW20PointerTest.js +++ b/contracts/test/ERC20toCW20PointerTest.js @@ -93,12 +93,27 @@ describe("ERC20 to CW20 Pointer", function () { expect(await pointer.balanceOf(sender.evmAddress)).to.equal(balances.account0); expect(await pointer.balanceOf(recipient.evmAddress)).to.equal(balances.account1); + const blockNumber = await ethers.provider.getBlockNumber(); const tx = await pointer.transfer(recipient.evmAddress, 1); await tx.wait(); expect(await pointer.balanceOf(sender.evmAddress)).to.equal(balances.account0-1); expect(await pointer.balanceOf(recipient.evmAddress)).to.equal(balances.account1+1); + // check logs + const filter = { + fromBlock: blockNumber, + toBlock: 'latest', + address: await pointer.getAddress(), + topics: [ethers.id("Transfer(address,address,uint256)")] + }; + const logs = await ethers.provider.getLogs(filter); + expect(logs.length).to.equal(1); + expect(logs[0]["address"]).to.equal(await pointer.getAddress()); + expect(logs[0]["topics"][0]).to.equal(ethers.id("Transfer(address,address,uint256)")); + expect(logs[0]["topics"][1].substring(26)).to.equal(sender.evmAddress.substring(2).toLowerCase()); + expect(logs[0]["topics"][2].substring(26)).to.equal(recipient.evmAddress.substring(2).toLowerCase()); + const cleanupTx = await pointer.connect(recipient.signer).transfer(sender.evmAddress, 1); await cleanupTx.wait(); }); @@ -124,10 +139,25 @@ describe("ERC20 to CW20 Pointer", function () { it("should approve", async function () { const owner = accounts[0].evmAddress; const spender = accounts[1].evmAddress; + const blockNumber = await ethers.provider.getBlockNumber(); const tx = await pointer.approve(spender, 1000000); await tx.wait(); const allowance = await pointer.allowance(owner, spender); expect(Number(allowance)).to.equal(1000000); + + // check logs + const filter = { + fromBlock: blockNumber, + toBlock: 'latest', + address: await pointer.getAddress(), + topics: [ethers.id("Approval(address,address,uint256)")] + }; + const logs = await ethers.provider.getLogs(filter); + expect(logs.length).to.equal(1); + expect(logs[0]["address"]).to.equal(await pointer.getAddress()); + expect(logs[0]["topics"][0]).to.equal(ethers.id("Approval(address,address,uint256)")); + expect(logs[0]["topics"][1].substring(26)).to.equal(owner.substring(2).toLowerCase()); + expect(logs[0]["topics"][2].substring(26)).to.equal(spender.substring(2).toLowerCase()); }); it("should lower approval", async function () { @@ -227,6 +257,113 @@ describe("ERC20 to CW20 Pointer", function () { await (await pointer.approve(spender.evmAddress, 0)).wait() }); }); + + describe("CW20 event logs", function () { + it("correctly handle synthetic log indexes", async function () { + const ERC20PreTransferFromWrapper = await ethers.getContractFactory("ERC20PreTransferFromWrapper") + const wrappedPointer = await ERC20PreTransferFromWrapper.deploy(await pointer.getAddress()); + + await wrappedPointer.waitForDeployment() + + let sender = accounts[0]; + let recipient = accounts[1]; + + expect(await pointer.balanceOf(sender.evmAddress)).to.equal( + balances.account0 + ); + expect(await pointer.balanceOf(recipient.evmAddress)).to.equal( + balances.account1 + ); + + const txCount = 15; + + const tx = await pointer.approve(await wrappedPointer.getAddress(), txCount); + const receipt = await tx.wait(); + + const nonce = await ethers.provider.getTransactionCount( + sender.evmAddress + ); + + const transfer = async (index) => { + let tx + try { + tx = await wrappedPointer.transferFrom(sender.evmAddress, recipient.evmAddress, 1, { + nonce: nonce + (index - 1), + }); + } catch (error) { + console.log(`Transfer ${index} send transaction failed`, error); + throw error; + } + + let receipt + try { + receipt = await tx.wait(); + } catch (error) { + console.log(`Transfer ${index} send transaction failed`, error); + throw error; + } + }; + + let promises = []; + for (let i = 1; i <= txCount; i++) { + promises.push(transfer(i)); + } + + const blockNumber = await ethers.provider.getBlockNumber(); + console.log("Fetch block number", blockNumber, typeof blockNumber); + + await Promise.all(promises); + + expect(await pointer.balanceOf(sender.evmAddress)).to.equal( + balances.account0 - txCount + ); + expect(await pointer.balanceOf(recipient.evmAddress)).to.equal( + balances.account1 + txCount + ); + + // check logs + const filter = { + fromBlock: blockNumber, + toBlock: "latest", + address: await pointer.getAddress(), + topics: [ethers.id("Transfer(address,address,uint256)")], + }; + + const logs = await ethers.provider.getLogs(filter); + expect(logs.length).to.equal(txCount); + + const byBlock = {}; + logs.forEach((log) => { + if (!byBlock[log.blockNumber]) { + byBlock[log.blockNumber] = []; + } + + byBlock[log.blockNumber].push(log); + }); + + // Sanity check to ensure we were able to generate a block with multiple logs + expect( + Object.entries(byBlock).some( + ([blockNumber, logs]) => logs.length > 1 + ) + ).to.be.true; + + Object.entries(byBlock).forEach( + ([blockNumber, logs]) => { + const logIndexes = {} + logs.forEach((log, index) => { + expect(logIndexes[log.index], `all log indexes in block #${blockNumber} should be unique but log's Index value ${log.index} for log at position ${index} has already been seen`).to.be.undefined; + logIndexes[log.index] = index + }) + } + ) + + const cleanupTx = await wrappedPointer + .connect(recipient.signer) + .transfer(sender.evmAddress, txCount); + await cleanupTx.wait(); + }); + }) }); } diff --git a/contracts/test/ERC721toCW721PointerTest.js b/contracts/test/ERC721toCW721PointerTest.js index 3ca1fe815..e6d21f32a 100644 --- a/contracts/test/ERC721toCW721PointerTest.js +++ b/contracts/test/ERC721toCW721PointerTest.js @@ -82,14 +82,24 @@ describe("ERC721 to CW721 Pointer", function () { describe("write", function(){ it("approve", async function () { + const blockNumber = await ethers.provider.getBlockNumber(); const approvedTxResp = await pointerAcc0.approve(accounts[1].evmAddress, 2) await approvedTxResp.wait() const approved = await pointerAcc0.getApproved(2); expect(approved).to.equal(accounts[1].evmAddress); - await expect(approvedTxResp) - .to.emit(pointerAcc0, 'Approval') - .withArgs(accounts[0].evmAddress, accounts[1].evmAddress, 2); + const filter = { + fromBlock: blockNumber, + toBlock: 'latest', + address: await pointerAcc1.getAddress(), + topics: [ethers.id("Approval(address,address,uint256)")] + }; + const logs = await ethers.provider.getLogs(filter); + expect(logs.length).to.equal(1); + expect(logs[0]["address"]).to.equal(await pointerAcc1.getAddress()); + expect(logs[0]["topics"][0]).to.equal(ethers.id("Approval(address,address,uint256)")); + expect(logs[0]["topics"][1].substring(26)).to.equal(accounts[0].evmAddress.substring(2).toLowerCase()); + expect(logs[0]["topics"][2].substring(26)).to.equal(accounts[1].evmAddress.substring(2).toLowerCase()); }); it("cannot approve token you don't own", async function () { @@ -99,11 +109,21 @@ describe("ERC721 to CW721 Pointer", function () { it("transfer from", async function () { // accounts[0] should transfer token id 2 to accounts[1] await mine(pointerAcc0.approve(accounts[1].evmAddress, 2)); + const blockNumber = await ethers.provider.getBlockNumber(); transferTxResp = await pointerAcc1.transferFrom(accounts[0].evmAddress, accounts[1].evmAddress, 2); await transferTxResp.wait(); - await expect(transferTxResp) - .to.emit(pointerAcc0, 'Transfer') - .withArgs(accounts[0].evmAddress, accounts[1].evmAddress, 2); + const filter = { + fromBlock: blockNumber, + toBlock: 'latest', + address: await pointerAcc1.getAddress(), + topics: [ethers.id("Transfer(address,address,uint256)")] + }; + const logs = await ethers.provider.getLogs(filter); + expect(logs.length).to.equal(1); + expect(logs[0]["address"]).to.equal(await pointerAcc1.getAddress()); + expect(logs[0]["topics"][0]).to.equal(ethers.id("Transfer(address,address,uint256)")); + expect(logs[0]["topics"][1].substring(26)).to.equal(accounts[1].evmAddress.substring(2).toLowerCase()); + expect(logs[0]["topics"][2].substring(26)).to.equal(accounts[1].evmAddress.substring(2).toLowerCase()); const balance0 = await pointerAcc0.balanceOf(accounts[0].evmAddress); expect(balance0).to.equal(0); const balance1 = await pointerAcc0.balanceOf(accounts[1].evmAddress); diff --git a/contracts/test/EVMCompatabilityTest.js b/contracts/test/EVMCompatabilityTest.js index a5f37c9a1..8ab8220ec 100644 --- a/contracts/test/EVMCompatabilityTest.js +++ b/contracts/test/EVMCompatabilityTest.js @@ -5,6 +5,7 @@ const { ethers, upgrades } = require('hardhat'); const { getImplementationAddress } = require('@openzeppelin/upgrades-core'); const { deployEvmContract, setupSigners, fundAddress, getCosmosTx, getEvmTx} = require("./lib") const axios = require("axios"); +const { default: BigNumber } = require("bignumber.js"); function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); @@ -398,6 +399,12 @@ describe("EVM Test", function () { const retrievedAmount = await evmTester.readFromStorage(0); expect(retrievedAmount).to.equal(BigInt(testAmount)); }); + + it("Should work for BLOBBASEFEE opcode", async function () { + + const blobBaseFee = await evmTester.getBlobBaseFee(); + expect(blobBaseFee).to.deep.equal(BigInt(1)); + }); }) describe("Historical query test", function() { @@ -814,7 +821,7 @@ describe("EVM Test", function () { fromBlock: blockStart, toBlock: blockEnd, }; - + const logs = await ethers.provider.getLogs(filter); expect(logs).to.be.an('array'); expect(logs.length).to.equal(numTxs); @@ -826,7 +833,7 @@ describe("EVM Test", function () { toBlock: blockEnd, topics: [ethers.id("DummyEvent(string,bool,address,uint256,bytes)")] }; - + const logs = await ethers.provider.getLogs(filter); expect(logs).to.be.an('array'); expect(logs.length).to.equal(numTxs); @@ -839,7 +846,7 @@ describe("EVM Test", function () { toBlock: blockEnd, topics: [ethers.id("DummyEvent(string,bool,address,uint256,bytes)")] }; - + const logs = await ethers.provider.getLogs(filter); const blockHash = logs[0].blockHash; @@ -867,9 +874,9 @@ describe("EVM Test", function () { paddedOwnerAddr, ] }; - + const logs = await ethers.provider.getLogs(filter1); - + expect(logs).to.be.an('array'); expect(logs.length).to.equal(numTxs); @@ -885,7 +892,7 @@ describe("EVM Test", function () { }; const logs2 = await ethers.provider.getLogs(filter1); - + expect(logs2).to.be.an('array'); expect(logs2.length).to.equal(numTxs); }); @@ -901,7 +908,7 @@ describe("EVM Test", function () { "0x0000000000000000000000000000000000000000000000000000000000000003", ] }; - + const logs1 = await ethers.provider.getLogs(filter1); expect(logs1).to.be.an('array'); expect(logs1.length).to.equal(1); @@ -948,7 +955,7 @@ describe("EVM Test", function () { ethers.id("nonexistent event string"), ] }; - + const logs = await ethers.provider.getLogs(filter); expect(logs).to.be.an('array'); expect(logs.length).to.equal(0); @@ -1052,7 +1059,7 @@ describe("EVM Test", function () { value: usei, }); await txResponse.wait(); // Wait for the transaction to be mined - + // Check that the contract received the ETH const contractBalance = await ethers.provider.getBalance(evmAddr); expect(contractBalance - initialBalance).to.equal(usei); diff --git a/contracts/test/EVMPrecompileTest.js b/contracts/test/EVMPrecompileTest.js old mode 100644 new mode 100755 index 10250bd9f..58e270986 --- a/contracts/test/EVMPrecompileTest.js +++ b/contracts/test/EVMPrecompileTest.js @@ -17,6 +17,44 @@ describe("EVM Precompile Tester", function () { admin = await getAdmin(); }) + describe("EVM Addr Precompile Tester", function () { + const AddrPrecompileContract = '0x0000000000000000000000000000000000001004'; + let addr; + + before(async function () { + const signer = accounts[0].signer + const contractABIPath = '../../precompiles/addr/abi.json'; + const contractABI = require(contractABIPath); + // Get a contract instance + addr = new ethers.Contract(AddrPrecompileContract, contractABI, signer); + }); + + it("Assosciates successfully", async function () { + const unassociatedWallet = hre.ethers.Wallet.createRandom(); + try { + await addr.getSeiAddr(unassociatedWallet.address); + expect.fail("Expected an error here since we look up an unassociated address"); + } catch (error) { + expect(error).to.have.property('message').that.includes('execution reverted'); + } + + const message = `Please sign this message to link your EVM and Sei addresses. No SEI will be spent as a result of this signature.\n\n`; + const messageLength = Buffer.from(message, 'utf8').length; + const signatureHex = await unassociatedWallet.signMessage(message); + + const sig = hre.ethers.Signature.from(signatureHex); + + const appendedMessage = `\x19Ethereum Signed Message:\n${messageLength}${message}`; + const associatedAddrs = await addr.associate(`0x${sig.v-27}`, sig.r, sig.s, appendedMessage) + const addrs = await associatedAddrs.wait(); + expect(addrs).to.not.be.null; + + // Verify that addresses are now associated. + const seiAddr = await addr.getSeiAddr(unassociatedWallet.address); + expect(seiAddr).to.not.be.null; + }); + }); + describe("EVM Gov Precompile Tester", function () { const GovPrecompileContract = '0x0000000000000000000000000000000000001006'; let gov; @@ -60,6 +98,10 @@ describe("EVM Precompile Tester", function () { const receipt = await setWithdraw.wait(); expect(receipt.status).to.equal(1); }); + it("Should query rewards and get non null response", async function () { + const rewards = await distribution.rewards(accounts[0].evmAddress) + expect(rewards).to.not.be.null; + }); }); // TODO: Update when we add staking query precompiles @@ -86,7 +128,21 @@ describe("EVM Precompile Tester", function () { }); const receipt = await delegate.wait(); expect(receipt.status).to.equal(1); - // TODO: Add staking query precompile here + + const delegation = await staking.delegation(accounts[0].evmAddress, validatorAddr); + expect(delegation).to.not.be.null; + expect(delegation[0][0]).to.equal(10000n); + + const undelegate = await staking.undelegate(validatorAddr, delegation[0][0]); + const undelegateReceipt = await undelegate.wait(); + expect(undelegateReceipt.status).to.equal(1); + + try { + await staking.delegation(accounts[0].evmAddress, validatorAddr); + expect.fail("Expected an error here since we undelegated the amount and delegation should not exist anymore."); + } catch (error) { + expect(error).to.have.property('message').that.includes('execution reverted'); + } }); }); diff --git a/contracts/test/lib.js b/contracts/test/lib.js index 2292a91a3..52590c742 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -101,6 +101,7 @@ async function importKey(name, keyfile) { } } +/** @type {(keyName: string) => { seiAddress: string, evmAddress: string }} */ async function getNativeAccount(keyName) { await associateKey(adminKeyName) const seiAddress = await getKeySeiAddress(keyName) @@ -375,6 +376,7 @@ async function queryWasm(contractAddress, operation, args={}){ return JSON.parse(output) } +/** @type {(contractAddress: string, msg: T, coins: string) => R} */ async function executeWasm(contractAddress, msg, coins = "0usei") { const jsonString = JSON.stringify(msg).replace(/"/g, '\\"'); // Properly escape JSON string const command = `seid tx wasm execute ${contractAddress} "${jsonString}" --amount ${coins} --from ${adminKeyName} --gas=5000000 --fees=1000000usei -y --broadcast-mode block -o json`; @@ -423,14 +425,14 @@ async function execute(command, interaction=`printf "12345678\\n"`){ return await execCommand(command); } -function execCommand(command) { +async function execCommand(command, options = {errorOnAnyStderrContent: true}) { return new Promise((resolve, reject) => { exec(command, (error, stdout, stderr) => { if (error) { reject(error); return; } - if (stderr) { + if (stderr && options.errorOnAnyStderrContent) { reject(new Error(stderr)); return; } @@ -457,6 +459,7 @@ module.exports = { instantiateWasm, createTokenFactoryTokenAndMint, execute, + execCommand, getSeiAddress, getEvmAddress, queryWasm, diff --git a/contracts/test/tracer/firehose/FirehoseTracerTest.js b/contracts/test/tracer/firehose/FirehoseTracerTest.js new file mode 100644 index 000000000..e5fb76d8b --- /dev/null +++ b/contracts/test/tracer/firehose/FirehoseTracerTest.js @@ -0,0 +1,366 @@ +const { + setupSigners, deployErc20PointerForCw20, getAdmin, execCommand, executeWasm, deployWasm, WASM, ABI, registerPointerForERC20, testAPIEnabled, + incrementPointerVersion, + isDocker +} = require("../../lib"); +const { expect } = require("chai"); +const BigNumber = require('bignumber.js'); + +/** + * @typedef SeiDuplexAccount + * @property {string} evmAddress + * @property {string} seiAddress + */ + +/** + * @typedef ResuableContractDeployment + * @property {SeiDuplexAccount} [admin] + * @property {string} [cw20Address] + * @property {string} [cw20EvmPointerAddress] + * @property {string} [wrappedCw20EvmPointerAddress] + */ +describe("Firehose tracer", function () { + let accounts; + /** @type {SeiDuplexAccount} */ + let admin; + let cw20Address; + let cw20EvmPointerAddress; + let cw20EvmPointerContract; + + // We have a link ERC20 -> CW20, this contract wraps the ERC20 to add an extra log when doing + // either a transfer or a transferFrom. An approval is required before transferFrom can be done + // as ERC20 rules. + // + // This extra log tests the case where a DeliverTxHook adds logs to a transaction that already contains + // log. + let wrappedCw20EvmPointerAddress; + let wrappedCw20EvmPointerContract; + + /** @type {ResuableContractDeployment} */ + let deployment + + const balances = { + admin: 1000000, + account0: 2000000, + account1: 3000000 + } + + before(async function () { + deployment = JSON.parse(process.env['SEI_TEST_REUSABLE_CONTRACTS'] ?? '{}') + + accounts = await setupSigners(await hre.ethers.getSigners()); + + if (deployment.admin) { + admin = deployment.admin; + } else { + admin = deployment.admin = await getAdmin(); + } + + if (deployment.cw20Address) { + cw20Address = deployment.cw20Address; + } else { + cw20Address = deployment.cw20Address = await deployWasm(WASM.CW20, accounts[0].seiAddress, "cw20", { + name: "Test", + symbol: "TEST", + decimals: 6, + initial_balances: [ + { address: admin.seiAddress, amount: "1000000" }, + { address: accounts[0].seiAddress, amount: "2000000" }, + { address: accounts[1].seiAddress, amount: "3000000" } + ], + mint: { + "minter": admin.seiAddress, "cap": "99900000000" + } + }); + } + + if (deployment.cw20EvmPointerAddress) { + cw20EvmPointerAddress = deployment.cw20EvmPointerAddress; + } else { + cw20EvmPointerAddress = deployment.cw20EvmPointerAddress = await deployErc20PointerForCw20(hre.ethers.provider, cw20Address); + } + + contract = new hre.ethers.Contract(cw20EvmPointerAddress, ABI.ERC20, hre.ethers.provider); + cw20EvmPointerContract = await contract.connect(accounts[0].signer); + + if (deployment.wrappedCw20EvmPointerAddress) { + wrappedCw20EvmPointerAddress = deployment.wrappedCw20EvmPointerAddress; + contract = new hre.ethers.Contract(wrappedCw20EvmPointerAddress, ABI.ERC20, hre.ethers.provider); + wrappedCw20EvmPointerContract = await contract.connect(accounts[0].signer); + } else { + contract = await ethers.getContractFactory("ERC20PreTransferFromWrapper") + wrappedCw20EvmPointerContract = await contract.deploy(await cw20EvmPointerAddress); + await wrappedCw20EvmPointerContract.waitForDeployment(); + wrappedCw20EvmPointerAddress = deployment.wrappedCw20EvmPointerAddress = await wrappedCw20EvmPointerContract.getAddress(); + } + + if ((process.env.SEI_TEST_REUSABLE_CONTRACTS || '').match(/^\?$/)) { + const asJson = JSON.stringify(deployment) + console.log(`export SEI_TEST_REUSABLE_CONTRACTS='${asJson}'`); + } + }); + + it("CW20 transfer performed through ERC20 pointer contract", async function () { + let sender = accounts[0]; + let recipient = accounts[1]; + + expect(await cw20EvmPointerContract.balanceOf(sender.evmAddress)).to.equal( + balances.account0 + ); + expect(await cw20EvmPointerContract.balanceOf(recipient.evmAddress)).to.equal( + balances.account1 + ); + + let tx = await cw20EvmPointerContract.approve(wrappedCw20EvmPointerAddress, 1); + let receipt = await tx.wait(); + + const startBlockNumber = await ethers.provider.getBlockNumber(); + + tx = await wrappedCw20EvmPointerContract.transferFrom(sender.evmAddress, recipient.evmAddress, 1); + receipt = await tx.wait(); + + // We can cleanup right now, we just check the logs + const cleanupTx = await cw20EvmPointerContract + .connect(recipient.signer) + .transfer(sender.evmAddress, 1); + await cleanupTx.wait(); + + const block = await getFirehoseBlock(receipt.blockNumber); + + const txHashLookedFor = receipt.hash.replace("0x", ""); + const trace = block.transaction_traces.find((trace) => trace.hash === txHashLookedFor); + + const logs = collectTrxCallsLogs(trace) + const receiptLogs = trace.receipt.logs + expect(logs.length).to.equal(2) + expect(logs.length, "Transaction calls logs and receipt logs count mismatch").to.equal(receiptLogs.length) + + assertTrxOrdinals(trace, receipt.blockNumber) + }); + + it("CW20 transfer on CW contract when ERC20 pointer exists leads to added logs on non-EVM transaction", async function () { + // *Important* The "amount" must be in string otherwise the contract execution fails! + const output = await executeWasm(cw20Address, { transfer: { recipient: cw20Address, amount: "100" } }) + if (output.code !== 0) { + throw new Error(`Failed transaction\n${JSON.stringify(output, null, 2)}`) + } + + // TODO: Perform cleanup by returning the fund! + + const blockNumber = output.height + const txHashLookedFor = evmHash(output.txhash) + + const firehoseBlock = await getFirehoseBlock(blockNumber) + const trace = firehoseBlock.transaction_traces.find((trace) => trace.hash === txHashLookedFor); + expect(trace.from).to.equal(evmAddr(admin.evmAddress)) + expect(trace.to).to.equal(evmAddr(cw20EvmPointerAddress)) + + expect(trace.calls).to.be.lengthOf(1) + + const call = trace.calls[0] + expect(call.caller).to.equal(evmAddr(admin.evmAddress)) + expect(call.address).to.equal(evmAddr(cw20EvmPointerAddress)) + + const logs = collectTrxCallsLogs(trace) + const receiptLogs = trace.receipt.logs + expect(logs.length).to.equal(1) + expect(logs.length, "Transaction calls logs and receipt logs count mismatch").to.equal(receiptLogs.length) + + expect(logs[0].address).to.equal(evmAddr(cw20EvmPointerAddress)) + + assertTrxOrdinals(trace, blockNumber) + }); + + it("CW20 transfer performed through ERC20 pointer contract, multiple trx bridged per block", async function () { + let sender = accounts[0]; + let recipient = accounts[1]; + + expect(await cw20EvmPointerContract.balanceOf(sender.evmAddress)).to.equal( + balances.account0 + ); + expect(await cw20EvmPointerContract.balanceOf(recipient.evmAddress)).to.equal( + balances.account1 + ); + + const txCount = 15; + + const tx = await cw20EvmPointerContract.approve(wrappedCw20EvmPointerAddress, txCount); + const receipt = await tx.wait(); + + const nonce = await ethers.provider.getTransactionCount( + sender.evmAddress + ); + + const transfer = async (index) => { + let tx + try { + tx = await wrappedCw20EvmPointerContract.transferFrom(sender.evmAddress, recipient.evmAddress, 1, { + nonce: nonce + (index - 1), + }); + } catch (error) { + console.log(`Transfer ${index} send transaction failed`, error); + throw error; + } + + let receipt + try { + receipt = await tx.wait(); + } catch (error) { + console.log(`Transfer ${index} send transaction failed`, error); + throw error; + } + }; + + let promises = []; + for (let i = 1; i <= txCount; i++) { + promises.push(transfer(i)); + } + + const blockNumber = await ethers.provider.getBlockNumber(); + + await Promise.all(promises); + + expect(await cw20EvmPointerContract.balanceOf(sender.evmAddress)).to.equal( + balances.account0 - txCount + ); + expect(await cw20EvmPointerContract.balanceOf(recipient.evmAddress)).to.equal( + balances.account1 + txCount + ); + + // check logs + const filter = { + fromBlock: blockNumber, + toBlock: "latest", + address: await cw20EvmPointerContract.getAddress(), + topics: [ethers.id("Transfer(address,address,uint256)")], + }; + + const logs = await ethers.provider.getLogs(filter); + expect(logs.length).to.equal(txCount); + + /** @type Record> */ + const byBlockThenTx = {}; + logs.forEach((log) => { + if (!byBlockThenTx[log.blockNumber]) { + byBlockThenTx[log.blockNumber] = {}; + } + + if (!byBlockThenTx[log.blockNumber][log.transactionHash]) { + byBlockThenTx[log.blockNumber][log.transactionHash] = []; + } + + byBlockThenTx[log.blockNumber][log.transactionHash].push(log); + }); + + // Sanity check to ensure we were able to generate a block with multiple logs + expect( + Object.entries(byBlockThenTx).some( + ([blockNumber, byTx]) => { + const logCountInBlock = Object.values(byTx).reduce((logCount, logsInTx) => logCount + logsInTx.length, 0) + + return logCountInBlock > 1 + } + ) + ).to.be.true; + + Object.entries(byBlockThenTx).forEach( + ([blockNumber, byTx]) => { + Object.entries(byTx).forEach( + ([txHash, logs]) => { + const logIndexes = {} + logs.forEach((log, index) => { + expect(logIndexes[log.index], `all log indexes in block tx ${txHash} (at block #${blockNumber}) should be unique but log's Index value ${log.index} for log at position ${index} has already been seen`).to.be.undefined; + logIndexes[log.index] = index + }) + } + ) + } + ) + + const cleanupTx = await cw20EvmPointerContract + .connect(recipient.signer) + .transfer(sender.evmAddress, txCount); + await cleanupTx.wait(); + }); +}); + +function evmAddr(input) { + return input.toLowerCase().replace("0x", "") +} + +function evmHash(input) { + return input.toLowerCase().replace("0x", "") +} + +function assertTrxOrdinals(trace, blockNumber) { + const ordinals = Object.entries(collectTrxOrdinals(trace)) + ordinals.sort(([a], [b]) => parseInt(a) - parseInt(b)) + + expect(ordinals.length).to.be.greaterThan(0); + + let previous = undefined; + ordinals.forEach(([ordinal, count]) => { + expect(count, `Ordinal ${ordinal} has been seen ${count} times throughout transaction ${trace.hash} at block ${blockNumber}, that is invalid`).to.equal(1); + + if (previous) { + expect(previous + 1, `Ordinal ${ordinal} should have strictly follow ${previous}, so that ${previous} + 1 == ${ordinal} which was not the case here`).to.be.equal(parseInt(ordinal)); + } + + previous = parseInt(ordinal); + }); +} + +function collectTrxCallsLogs(firehoseTrx) { + const logs = []; + firehoseTrx.calls.forEach((call) => { + (call.logs || []).forEach((log) => { + logs.push(log) + }) + }) + + return logs +} + +function collectTrxOrdinals(firehoseTrx) { + /** @type { Record } */ + const ordinals = {}; + + ordinals[firehoseTrx.begin_ordinal || 0] = (ordinals[firehoseTrx.begin_ordinal || 0] || 0) + 1 + ordinals[firehoseTrx.end_ordinal || 0] = (ordinals[firehoseTrx.end_ordinal || 0] || 0) + 1 + + firehoseTrx.calls.forEach((call) => { + collectCallOrdinals(ordinals, call) + }) + + return ordinals +} + +/** @type { (ordinals: Record, call: unknown) } */ +function collectCallOrdinals(ordinals, call) { + ordinals[call.begin_ordinal || 0] = (ordinals[call.begin_ordinal || 0] || 0) + 1 + ordinals[call.end_ordinal || 0] = (ordinals[call.end_ordinal || 0] || 0) + 1 + + collectChangesOrdinals(ordinals, call.logs || []) + collectChangesOrdinals(ordinals, call.account_creations || []) + collectChangesOrdinals(ordinals, call.balance_changes || []) + collectChangesOrdinals(ordinals, call.gas_changes || []) + collectChangesOrdinals(ordinals, call.nonce_changes || []) + collectChangesOrdinals(ordinals, call.storage_changes || []) + collectChangesOrdinals(ordinals, call.code_changes || []) +} + +function collectChangesOrdinals(ordinals, changes) { + changes.forEach((change) => { + ordinals[change.ordinal || 0] = (ordinals[change.ordinal || 0] || 0) + 1 + }) +} + +async function getFirehoseBlock(blockNumber) { + if (await isDocker()) { + throw new Error("FirehoseTracerTest can only be run outside of docker for now"); + } + + const command = `fireeth tools firehose-client -p localhost:8089 ${blockNumber}:+0` + const output = await execCommand(command, { errorOnAnyStderrContent: false }); + return JSON.parse(output).block; +} diff --git a/evmrpc/block.go b/evmrpc/block.go index f5fdc75e5..1525618dd 100644 --- a/evmrpc/block.go +++ b/evmrpc/block.go @@ -128,7 +128,10 @@ func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.Block mtx.Unlock() } } else { - encodedReceipt, err := encodeReceipt(receipt, a.txConfig.TxDecoder(), block) + encodedReceipt, err := encodeReceipt(receipt, a.txConfig.TxDecoder(), block, func(h common.Hash) bool { + _, err := a.keeper.GetReceipt(a.ctxProvider(height), h) + return err == nil + }) if err != nil { mtx.Lock() returnErr = err diff --git a/evmrpc/setup_test.go b/evmrpc/setup_test.go index 73b861e38..3d6ff7ddb 100644 --- a/evmrpc/setup_test.go +++ b/evmrpc/setup_test.go @@ -871,6 +871,12 @@ func formatParam(p interface{}) string { kvs = append(kvs, fmt.Sprintf("\"%s\":%s", k, formatParam(v))) } return fmt.Sprintf("{%s}", strings.Join(kvs, ",")) + case map[string]map[string]interface{}: + kvs := []string{} + for k, v := range v { + kvs = append(kvs, fmt.Sprintf("\"%s\":%s", k, formatParam(v))) + } + return fmt.Sprintf("{%s}", strings.Join(kvs, ",")) default: panic("did not match on type") } diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index 6f383288c..428ebc0ac 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -5,8 +5,11 @@ import ( "errors" "fmt" "math/big" + "strings" "time" + "github.com/sei-protocol/sei-chain/utils/helpers" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -24,7 +27,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/utils/metrics" - "github.com/sei-protocol/sei-chain/x/evm/ante" "github.com/sei-protocol/sei-chain/x/evm/keeper" "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/sei-protocol/sei-chain/x/evm/types" @@ -89,6 +91,15 @@ func (s *SimulationAPI) EstimateGas(ctx context.Context, args ethapi.Transaction func (s *SimulationAPI) Call(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *ethapi.StateOverride, blockOverrides *ethapi.BlockOverrides) (result hexutil.Bytes, returnErr error) { startTime := time.Now() defer recordMetrics("eth_call", s.connectionType, startTime, returnErr == nil) + defer func() { + if r := recover(); r != nil { + if strings.Contains(fmt.Sprintf("%s", r), "Int overflow") { + returnErr = errors.New("error: balance override overflow") + } else { + returnErr = fmt.Errorf("something went wrong: %v", r) + } + } + }() if blockNrOrHash == nil { latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) blockNrOrHash = &latest @@ -186,7 +197,10 @@ func (b *Backend) GetTransaction(ctx context.Context, txHash common.Hash) (tx *e txIndex := hexutil.Uint(receipt.TransactionIndex) tmTx := block.Block.Txs[int(txIndex)] // We need to find the ethIndex - evmTxIndex, found := GetEvmTxIndex(block.Block.Txs, receipt.TransactionIndex, b.txDecoder) + evmTxIndex, found := GetEvmTxIndex(block.Block.Txs, receipt.TransactionIndex, b.txDecoder, func(h common.Hash) bool { + _, err := b.keeper.GetReceipt(sdkCtx, h) + return err == nil + }) if !found { return nil, common.Hash{}, 0, 0, errors.New("failed to find transaction in block") } @@ -307,7 +321,7 @@ func (b *Backend) StateAtTransaction(ctx context.Context, block *ethtypes.Block, metrics.IncrementAssociationError("state_at_tx", err) return nil, vm.BlockContext{}, nil, nil, err } - if err := ante.NewEVMPreprocessDecorator(b.keeper, b.keeper.AccountKeeper()).AssociateAddresses(statedb.Ctx(), seiAddr, msg.From, nil); err != nil { + if err := helpers.NewAssociationHelper(b.keeper, b.keeper.BankKeeper(), b.keeper.AccountKeeper()).AssociateAddresses(statedb.Ctx(), seiAddr, msg.From, nil); err != nil { return nil, vm.BlockContext{}, nil, nil, err } } @@ -346,7 +360,7 @@ func (b *Backend) StateAtBlock(ctx context.Context, block *ethtypes.Block, reexe metrics.IncrementAssociationError("state_at_block", err) return nil, emptyRelease, err } - if err := ante.NewEVMPreprocessDecorator(b.keeper, b.keeper.AccountKeeper()).AssociateAddresses(statedb.Ctx(), seiAddr, msg.From, nil); err != nil { + if err := helpers.NewAssociationHelper(b.keeper, b.keeper.BankKeeper(), b.keeper.AccountKeeper()).AssociateAddresses(statedb.Ctx(), seiAddr, msg.From, nil); err != nil { return nil, emptyRelease, err } } @@ -396,12 +410,14 @@ func (b *Backend) getBlockHeight(ctx context.Context, blockNrOrHash rpc.BlockNum } func (b *Backend) getHeader(blockNumber *big.Int) *ethtypes.Header { + zeroExcessBlobGas := uint64(0) header := ðtypes.Header{ - Difficulty: common.Big0, - Number: blockNumber, - BaseFee: b.keeper.GetBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).BigInt(), - GasLimit: b.config.GasCap, - Time: uint64(time.Now().Unix()), + Difficulty: common.Big0, + Number: blockNumber, + BaseFee: b.keeper.GetBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).BigInt(), + GasLimit: b.config.GasCap, + Time: uint64(time.Now().Unix()), + ExcessBlobGas: &zeroExcessBlobGas, } number := blockNumber.Int64() block, err := blockByNumber(context.Background(), b.tmClient, &number) diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index b626ba637..5375722f6 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -132,6 +132,31 @@ func TestCall(t *testing.T) { Ctx = Ctx.WithBlockHeight(8) } +func TestEthCallHighAmount(t *testing.T) { + Ctx = Ctx.WithBlockHeight(1) + _, from := testkeeper.MockAddressPair() + _, to := testkeeper.MockAddressPair() + txArgs := map[string]interface{}{ + "from": from.Hex(), + "to": to.Hex(), + "value": "0x0", + "nonce": "0x2", + "chainId": fmt.Sprintf("%#x", EVMKeeper.ChainID(Ctx)), + } + + overrides := map[string]map[string]interface{}{ + from.Hex(): {"balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + } + resObj := sendRequestGood(t, "call", txArgs, "latest", overrides) + fmt.Println("resObj = ", resObj) + errMap := resObj["error"].(map[string]interface{}) + result := errMap["message"] + fmt.Println("res = ", result) + require.Equal(t, result, "error: balance override overflow") + + Ctx = Ctx.WithBlockHeight(8) +} + func TestNewRevertError(t *testing.T) { err := evmrpc.NewRevertError(&core.ExecutionResult{}) require.NotNil(t, err) diff --git a/evmrpc/subscribe.go b/evmrpc/subscribe.go index f7b2bbe0b..35c8e2c3c 100644 --- a/evmrpc/subscribe.go +++ b/evmrpc/subscribe.go @@ -144,7 +144,10 @@ func (a *SubscriptionAPI) Logs(ctx context.Context, filter *filters.FilterCriter if !supported { return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } - + // create empty filter if filter does not exist + if filter == nil { + filter = &filters.FilterCriteria{} + } rpcSub := notifier.CreateSubscription() if filter.BlockHash != nil { diff --git a/evmrpc/subscribe_test.go b/evmrpc/subscribe_test.go index f64e44fc8..effaf5fc3 100644 --- a/evmrpc/subscribe_test.go +++ b/evmrpc/subscribe_test.go @@ -83,6 +83,24 @@ func TestSubscribeNewHeads(t *testing.T) { } } +func TestSubscribeEmptyLogs(t *testing.T) { + t.Parallel() + recvCh, done := sendWSRequestGood(t, "subscribe", "logs") + defer func() { done <- struct{}{} }() + + timer := time.NewTimer(2 * time.Second) + + // just testing to see that we don't crash when no params are provided + for { + select { + case _ = <-recvCh: + return + case <-timer.C: + t.Fatal("No message received within 5 seconds") + } + } +} + func TestSubscribeNewLogs(t *testing.T) { t.Parallel() data := map[string]interface{}{ diff --git a/evmrpc/tx.go b/evmrpc/tx.go index 805d670fb..9d773c872 100644 --- a/evmrpc/tx.go +++ b/evmrpc/tx.go @@ -2,6 +2,7 @@ package evmrpc import ( "context" + "crypto/sha256" "errors" "math/big" "strings" @@ -42,7 +43,8 @@ func NewTransactionAPI(tmClient rpcclient.Client, k *keeper.Keeper, ctxProvider func (t *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (result map[string]interface{}, returnErr error) { startTime := time.Now() defer recordMetrics("eth_getTransactionReceipt", t.connectionType, startTime, returnErr == nil) - receipt, err := t.keeper.GetReceipt(t.ctxProvider(LatestCtxHeight), hash) + sdkctx := t.ctxProvider(LatestCtxHeight) + receipt, err := t.keeper.GetReceipt(sdkctx, hash) if err != nil { if strings.Contains(err.Error(), "not found") { // When the transaction doesn't exist, the RPC method should return JSON null @@ -56,7 +58,10 @@ func (t *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common. if err != nil { return nil, err } - return encodeReceipt(receipt, t.txConfig.TxDecoder(), block) + return encodeReceipt(receipt, t.txConfig.TxDecoder(), block, func(h common.Hash) bool { + _, err := t.keeper.GetReceipt(sdkctx, h) + return err == nil + }) } func (t *TransactionAPI) GetVMError(hash common.Hash) (result string, returnErr error) { @@ -240,36 +245,33 @@ func getEthTxForTxBz(tx tmtypes.Tx, decoder sdk.TxDecoder) *ethtypes.Transaction // Gets the EVM tx index based on the tx index (typically from receipt.TransactionIndex // Essentially loops through and calculates the index if we ignore cosmos txs -func GetEvmTxIndex(txs tmtypes.Txs, txIndex uint32, decoder sdk.TxDecoder) (index int, found bool) { - evmTxIndex := 0 - foundTx := false +func GetEvmTxIndex(txs tmtypes.Txs, txIndex uint32, decoder sdk.TxDecoder, receiptChecker func(common.Hash) bool) (index int, found bool) { + var evmTxIndex int for i, tx := range txs { etx := getEthTxForTxBz(tx, decoder) - if etx == nil { // cosmos tx, skip + // does not exist and has no receipt (cosmos) + if etx == nil && !receiptChecker(sha256.Sum256(tx)) { continue } + + // found the index if i == int(txIndex) { - foundTx = true - break + return evmTxIndex, true } - evmTxIndex++ - } - if !foundTx { - return -1, false - } else { - return evmTxIndex, true + evmTxIndex++ } + return -1, false } -func encodeReceipt(receipt *types.Receipt, decoder sdk.TxDecoder, block *coretypes.ResultBlock) (map[string]interface{}, error) { +func encodeReceipt(receipt *types.Receipt, decoder sdk.TxDecoder, block *coretypes.ResultBlock, receiptChecker func(common.Hash) bool) (map[string]interface{}, error) { blockHash := block.BlockID.Hash bh := common.HexToHash(blockHash.String()) logs := keeper.GetLogsForTx(receipt) for _, log := range logs { log.BlockHash = bh } - evmTxIndex, foundTx := GetEvmTxIndex(block.Block.Txs, receipt.TransactionIndex, decoder) + evmTxIndex, foundTx := GetEvmTxIndex(block.Block.Txs, receipt.TransactionIndex, decoder, receiptChecker) // convert tx index including cosmos txs to tx index excluding cosmos txs if !foundTx { return nil, errors.New("failed to find transaction in block") diff --git a/example/cosmwasm/cw20/artifacts/checksums.txt b/example/cosmwasm/cw20/artifacts/checksums.txt index c6f576df5..8d57b82dd 100644 --- a/example/cosmwasm/cw20/artifacts/checksums.txt +++ b/example/cosmwasm/cw20/artifacts/checksums.txt @@ -1 +1 @@ -a25d78d7acd2ee47cc39c224e162fe79b53e6bbe6ed2a56e8c0a86593ebe6102 cwerc20.wasm +e9b4fe3e450fc29f1a4ad3fc65217a687e24a36ab2bc76a673e5593b92c0a1d7 cwerc20.wasm diff --git a/example/cosmwasm/cw20/artifacts/cwerc20.wasm b/example/cosmwasm/cw20/artifacts/cwerc20.wasm index aac641633..b6c821c19 100644 Binary files a/example/cosmwasm/cw20/artifacts/cwerc20.wasm and b/example/cosmwasm/cw20/artifacts/cwerc20.wasm differ diff --git a/example/cosmwasm/cw20/src/contract.rs b/example/cosmwasm/cw20/src/contract.rs index b5b2a09a1..46c289704 100644 --- a/example/cosmwasm/cw20/src/contract.rs +++ b/example/cosmwasm/cw20/src/contract.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{ }; use cw20::{Cw20ReceiveMsg, AllowanceResponse}; use cw_utils::Expiration; -use crate::msg::{cw20receive_into_cosmos_msg, EvmMsg, EvmQueryWrapper, ExecuteMsg, QueryMsg, InstantiateMsg}; +use crate::msg::{cw20receive_into_cosmos_msg, EvmMsg, EvmQueryWrapper, ExecuteMsg, QueryMsg, InstantiateMsg, MigrateMsg}; use crate::querier::EvmQuerier; use crate::error::ContractError; use crate::state::ERC20_ADDRESS; @@ -21,6 +21,15 @@ pub fn instantiate( Ok(Response::default()) } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate( + _deps: DepsMut, + _env: Env, + _msg: MigrateMsg, +) -> Result { + Ok(Response::default()) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, diff --git a/example/cosmwasm/cw20/src/msg.rs b/example/cosmwasm/cw20/src/msg.rs index 1e63b8f55..6c86317c0 100644 --- a/example/cosmwasm/cw20/src/msg.rs +++ b/example/cosmwasm/cw20/src/msg.rs @@ -11,6 +11,9 @@ pub struct InstantiateMsg { pub erc20_address: String, } +#[cw_serde] +pub struct MigrateMsg {} + /// SeiRoute is enum type to represent sei query route path #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] diff --git a/example/cosmwasm/cw721/artifacts/checksums.txt b/example/cosmwasm/cw721/artifacts/checksums.txt index c02325416..6bcb7fbb5 100644 --- a/example/cosmwasm/cw721/artifacts/checksums.txt +++ b/example/cosmwasm/cw721/artifacts/checksums.txt @@ -1 +1 @@ -4355b9f7c6a3337ee6994512155435bdfc80bd151a57e78fd50f9d41e62a8aaa cwerc721.wasm +9c685821723e723bf949314d4511ef2695a1440d8e33eecf30e11bb8b6b2a445 cwerc721.wasm diff --git a/example/cosmwasm/cw721/artifacts/cwerc721.wasm b/example/cosmwasm/cw721/artifacts/cwerc721.wasm index a1cfac086..379ede8a8 100644 Binary files a/example/cosmwasm/cw721/artifacts/cwerc721.wasm and b/example/cosmwasm/cw721/artifacts/cwerc721.wasm differ diff --git a/example/cosmwasm/cw721/src/contract.rs b/example/cosmwasm/cw721/src/contract.rs index f7515787e..2d09a97ca 100644 --- a/example/cosmwasm/cw721/src/contract.rs +++ b/example/cosmwasm/cw721/src/contract.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{ use cw721::{Cw721ReceiveMsg, OwnerOfResponse, Approval, ApprovalResponse, ApprovalsResponse, OperatorResponse, ContractInfoResponse, NftInfoResponse, AllNftInfoResponse, TokensResponse, OperatorsResponse, NumTokensResponse}; use cw2981_royalties::msg::{RoyaltiesInfoResponse, CheckRoyaltiesResponse}; use cw2981_royalties::{Metadata as Cw2981Metadata, Extension as Cw2981Extension}; -use crate::msg::{EvmQueryWrapper, EvmMsg, InstantiateMsg, ExecuteMsg, QueryMsg, CwErc721QueryMsg}; +use crate::msg::{EvmQueryWrapper, EvmMsg, InstantiateMsg, ExecuteMsg, QueryMsg, CwErc721QueryMsg, MigrateMsg}; use crate::querier::{EvmQuerier, DEFAULT_LIMIT, MAX_LIMIT}; use crate::error::ContractError; use crate::state::ERC721_ADDRESS; @@ -25,6 +25,15 @@ pub fn instantiate( Ok(Response::default()) } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate( + _deps: DepsMut, + _env: Env, + _msg: MigrateMsg, +) -> Result { + Ok(Response::default()) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, diff --git a/example/cosmwasm/cw721/src/msg.rs b/example/cosmwasm/cw721/src/msg.rs index d303c35ee..fd0d889fc 100644 --- a/example/cosmwasm/cw721/src/msg.rs +++ b/example/cosmwasm/cw721/src/msg.rs @@ -10,6 +10,9 @@ pub struct InstantiateMsg { pub erc721_address: String, } +#[cw_serde] +pub struct MigrateMsg {} + /// SeiRoute is enum type to represent sei query route path #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] diff --git a/go.mod b/go.mod index 451ce8ef8..16142d8af 100644 --- a/go.mod +++ b/go.mod @@ -344,17 +344,17 @@ require ( ) replace ( - github.com/CosmWasm/wasmd => github.com/sei-protocol/sei-wasmd v0.1.9 + github.com/CosmWasm/wasmd => github.com/sei-protocol/sei-wasmd v0.2.3 github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.3.24 + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.3.31 github.com/cosmos/iavl => github.com/sei-protocol/sei-iavl v0.1.9 - github.com/cosmos/ibc-go/v3 => github.com/sei-protocol/sei-ibc-go/v3 v3.3.1 + github.com/cosmos/ibc-go/v3 => github.com/sei-protocol/sei-ibc-go/v3 v3.3.2 github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-22 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/sei-protocol/sei-db => github.com/sei-protocol/sei-db v0.0.40 // Latest goleveldb is broken, we have to stick to this version github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.3.4 + github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.3.6 github.com/tendermint/tm-db => github.com/sei-protocol/tm-db v0.0.4 google.golang.org/grpc => google.golang.org/grpc v1.33.2 ) diff --git a/go.sum b/go.sum index a3cc0a21a..d35f21e68 100644 --- a/go.sum +++ b/go.sum @@ -1347,20 +1347,20 @@ github.com/sei-protocol/go-ethereum v1.13.5-sei-22 h1:t/m1qXER+DEMrcpqgoYmUxifkA github.com/sei-protocol/go-ethereum v1.13.5-sei-22/go.mod h1:kcRZmuzRn1lVejiFNTz4l4W7imnpq1bDAnuKS/RyhbQ= github.com/sei-protocol/goutils v0.0.2 h1:Bfa7Sv+4CVLNM20QcpvGb81B8C5HkQC/kW1CQpIbXDA= github.com/sei-protocol/goutils v0.0.2/go.mod h1:iYE2DuJfEnM+APPehr2gOUXfuLuPsVxorcDO+Tzq9q8= -github.com/sei-protocol/sei-cosmos v0.3.24 h1:7pvXNvsQ3P6wovH2Z6wK/G9UuRTHYgQtQNpPjt9ypZQ= -github.com/sei-protocol/sei-cosmos v0.3.24/go.mod h1:og/KbejR/zSQ8otapODEDU9zYNhFSUDbq9+tgeYePyU= +github.com/sei-protocol/sei-cosmos v0.3.31 h1:8qnubgQd83F2/Pxx5DmNH2vnl1XtTOAFbW44cEHbeCo= +github.com/sei-protocol/sei-cosmos v0.3.31/go.mod h1:og/KbejR/zSQ8otapODEDU9zYNhFSUDbq9+tgeYePyU= github.com/sei-protocol/sei-db v0.0.40 h1:s6B3u9u0r2Ypd67P8Lrz2IR/QU/FXwtS2X/fnYEix2g= github.com/sei-protocol/sei-db v0.0.40/go.mod h1:F/ZKZA8HJPcUzSZPA8yt6pfwlGriJ4RDR4eHKSGLStI= github.com/sei-protocol/sei-iavl v0.1.9 h1:y4mVYftxLNRs6533zl7N0/Ch+CzRQc04JDfHolIxgBE= github.com/sei-protocol/sei-iavl v0.1.9/go.mod h1:7PfkEVT5dcoQE+s/9KWdoXJ8VVVP1QpYYPLdxlkSXFk= -github.com/sei-protocol/sei-ibc-go/v3 v3.3.1 h1:BPG9LWe27x3SATpY9nj8JPe+0igyKyrcpB0z2ZvdcXQ= -github.com/sei-protocol/sei-ibc-go/v3 v3.3.1/go.mod h1:VwB/vWu4ysT5DN2aF78d17LYmx3omSAdq6gpKvM7XRA= -github.com/sei-protocol/sei-tendermint v0.3.4 h1:pAMXB2Cd0/rmmEkPgcEdIEjw7k64K7+cR2/2IuWBmM4= -github.com/sei-protocol/sei-tendermint v0.3.4/go.mod h1:4LSlJdhl3nf3OmohliwRNUFLOB1XWlrmSodrIP7fLh4= +github.com/sei-protocol/sei-ibc-go/v3 v3.3.2 h1:BaMZ6gjwqe3R/5dLmcJ1TkSZ3omcWy2TjaAZAeOJH44= +github.com/sei-protocol/sei-ibc-go/v3 v3.3.2/go.mod h1:VwB/vWu4ysT5DN2aF78d17LYmx3omSAdq6gpKvM7XRA= +github.com/sei-protocol/sei-tendermint v0.3.6 h1:bvvyWHyJcdCo6+b5Da8p9STyC+7tSNKAI9XPQsYgBuU= +github.com/sei-protocol/sei-tendermint v0.3.6/go.mod h1:4LSlJdhl3nf3OmohliwRNUFLOB1XWlrmSodrIP7fLh4= github.com/sei-protocol/sei-tm-db v0.0.5 h1:3WONKdSXEqdZZeLuWYfK5hP37TJpfaUa13vAyAlvaQY= github.com/sei-protocol/sei-tm-db v0.0.5/go.mod h1:Cpa6rGyczgthq7/0pI31jys2Fw0Nfrc+/jKdP1prVqY= -github.com/sei-protocol/sei-wasmd v0.1.9 h1:GrqrJgvLkzi+e/xhNFahYSo6ud8l4YjKSFm+FIDGW8k= -github.com/sei-protocol/sei-wasmd v0.1.9/go.mod h1:SymGi9pEIcsA8R4Stx7NyTDBWB+pp0LckftpkHwT6tM= +github.com/sei-protocol/sei-wasmd v0.2.3 h1:txB7LIx4Luh7+fxVN4/PiHEAjYnkutKyrpsuIFW8lmA= +github.com/sei-protocol/sei-wasmd v0.2.3/go.mod h1:EnQkqvUA3tYpdgXjqatHK8ym9LCm1z+lM7XMqR9SA3o= github.com/sei-protocol/tm-db v0.0.4 h1:7Y4EU62Xzzg6wKAHEotm7SXQR0aPLcGhKHkh3qd0tnk= github.com/sei-protocol/tm-db v0.0.4/go.mod h1:PWsIWOTwdwC7Ow/GUvx8HgUJTO691pBuorIQD8JvwAs= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= diff --git a/integration_test/firehose_test/.gitignore b/integration_test/firehose_test/.gitignore new file mode 100644 index 000000000..95233449f --- /dev/null +++ b/integration_test/firehose_test/.gitignore @@ -0,0 +1,2 @@ +.fireeth.log +.firehose-data \ No newline at end of file diff --git a/integration_test/firehose_test/bootstrap.sh b/integration_test/firehose_test/bootstrap.sh new file mode 100644 index 000000000..476355e4a --- /dev/null +++ b/integration_test/firehose_test/bootstrap.sh @@ -0,0 +1,19 @@ + +ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +SCRIPTS="$ROOT/../../scripts" + +set -e + +main() { + NO_RUN=1 $SCRIPTS/initialize_local_chain.sh + + if ! command -v "sd" &> /dev/null; then + echo "The 'sd' command is required for this script, please install it" + echo "by following instructions at https://github.com/chmln/sd?tab=readme-ov-file#installation" + exit 1 + fi + + sd '\[evm\]' "[evm]\nlive_evm_tracer = \"firehose\"" "$HOME/.sei/config/app.toml" +} + +main "$@" diff --git a/integration_test/firehose_test/run_test.sh b/integration_test/firehose_test/run_test.sh new file mode 100755 index 000000000..1733cf45e --- /dev/null +++ b/integration_test/firehose_test/run_test.sh @@ -0,0 +1,151 @@ + +ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +CONTRACTS="$ROOT/../../contracts" +TOP_PID=$$ + +set -e + +main() { + trap "exit 1" TERM + + data_dir="$ROOT/.firehose-data" + seid="${SEID:-seid}" + seid_args="start --home \"$HOME/.sei\" --trace --chain-id sei-chain" + fireeth_log="$ROOT/.fireeth.log" + start_firehose="true" + + while getopts "s" opt; do + case $opt in + s) start_firehose=false;; + esac + done + shift $((OPTIND-1)) + + fireeth="fireeth" + if ! command -v "$fireeth" &> /dev/null; then + echo "The '$fireeth' could not be found, you can install it through one of those means." + echo "" + echo "- By running 'brew install streamingfast/tap/firehose-ethereum' on Mac or Linux system (with Homebrew installed)" + echo "- By downloading a pre-compiled binary from https://github.com/streamingfast/firehose-ethereum/releases" + echo "- By building it from source cloning https://github.com/streamingfast/firehose-ethereum.git and then 'go install ./cmd/fireeth'" + exit 1 + fi + + if [[ $start_firehose == "true" ]]; then + echo "Running Sei node with Firehose tracer activated via 'fireeth'" + rm -rf "$data_dir" + + ("$fireeth" \ + start \ + reader-node,relayer,merger,firehose \ + -c '' \ + -d "$data_dir" \ + --common-first-streamable-block=1 \ + --reader-node-path="$seid" \ + --reader-node-arguments="$seid_args" \ + --reader-node-bootstrap-data-url="bash://$ROOT/bootstrap.sh" \ + --firehose-grpc-listen-addr="localhost:8089" &> "$fireeth_log") & + fireeth_pid=$! + trap "cleanup" EXIT + + monitor "fireeth" $fireeth_pid "$fireeth_log" & + + echo "Waiting for Firehose instance to be ready" + wait_for_firehose_ready "$fireeth_log" + + echo "Firehose instance is ready" + fi + + echo "Running Firehose tests" + cd "$CONTRACTS" + npx hardhat test --network seilocal test/tracer/firehose/FirehoseTracerTest.js +} + +cleanup() { + for job in `jobs -p`; do + kill $job &> /dev/null + wait $job &> /dev/null || true + done +} + +wait_for_firehose_ready() { + firehose_log="$1" + + for i in {1..8}; do + if grep -q '(firehose) launching gRPC server' "$firehose_log"; then + break + fi + + if [[ $i -eq 8 ]]; then + >&2 echo "The 'fireeth' instance did not start within ~30s which is not expected." + >&2 echo "" + show_logs_preview "$firehose_log" + kill -s TERM $TOP_PID + fi + + sleep $i + done + + # Sleep a bit again to ensure the server is fully started + sleep 1 +} + +# usage +monitor() { + name="$1" + pid="$2" + process_log="$3" + + while true; do + if ! kill -0 $pid &> /dev/null; then + sleep 2 + + echo "Process $name ($pid) died, exiting parent" + if [[ "$process_log" != "" ]]; then + show_logs_preview "$process_log" + fi + + kill -s TERM $TOP_PID &> /dev/null + exit 0 + fi + + sleep 1 + done +} + +show_logs_preview() { + log_file="$1" + + >&2 echo "Here the first 25 lines followed by the last 25 lines of the log:" + >&2 head -n 25 "$log_file" + >&2 echo "\n...\n" + >&2 tail -n 25 "$log_file" + + >&2 echo "" + >&2 echo "See full logs with 'less `relpath $log_file`'" +} + +extract() { + set +e + output=`echo "$1" | jq -r "$2"` + if [ $? -ne 0 ]; then + >&2 echo "Failed to extract from: $1" + >&2 echo "JQ: $2" + kill -s TERM $TOP_PID + fi + + echo "$output" + set -e +} + +relpath() { + if [[ $1 =~ /* ]]; then + # Works only if path is already absolute and do not contain , + echo "$1" | sed s,$PWD,.,g + else + # Print as-is + echo $1 + fi +} + +main "$@" diff --git a/integration_test/staking_module/staking_test.yaml b/integration_test/staking_module/staking_test.yaml index 370e518d4..8b07e1250 100644 --- a/integration_test/staking_module/staking_test.yaml +++ b/integration_test/staking_module/staking_test.yaml @@ -83,7 +83,7 @@ - cmd: seid q staking validator $SEI_NODE_1_VAL_ADDR --output json | jq -r ".tokens" env: BEFORE_STAKED_TOKENS - - cmd: printf "12345678\n" | seid tx staking unbond $SEI_NODE_1_VAL_ADDR 1sei --fees 2000usei --from admin -b block -y --output json | jq -r ".code" + - cmd: printf "12345678\n" | seid tx staking unbond $SEI_NODE_1_VAL_ADDR 1sei --fees 4000usei --gas 400000 --from admin -b block -y --output json | jq -r ".code" env: DELEGATE_RESPONSE_CODE # state after staking diff --git a/precompiles/addr/Addr.sol b/precompiles/addr/Addr.sol index d479747da..f24a26659 100644 --- a/precompiles/addr/Addr.sol +++ b/precompiles/addr/Addr.sol @@ -8,6 +8,14 @@ IAddr constant ADDR_CONTRACT = IAddr( ); interface IAddr { + // Transactions + function associate( + string memory v, + string memory r, + string memory s, + string memory customMessage + ) external returns (string seiAddr, address evmAddr); + // Queries function getSeiAddr(address addr) external view returns (string memory response); function getEvmAddr(string memory addr) external view returns (address response); diff --git a/precompiles/addr/abi.json b/precompiles/addr/abi.json index b9a6de290..211faabec 100755 --- a/precompiles/addr/abi.json +++ b/precompiles/addr/abi.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"v","type":"string"},{"internalType":"string","name":"r","type":"string"},{"internalType":"string","name":"s","type":"string"},{"internalType":"string","name":"customMessage","type":"string"}],"name":"associate","outputs":[{"internalType":"string","name":"seiAddr","type":"string"},{"internalType":"address","name":"evmAddr","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/addr/addr.go b/precompiles/addr/addr.go index cd35c5ec5..506b363af 100644 --- a/precompiles/addr/addr.go +++ b/precompiles/addr/addr.go @@ -3,9 +3,16 @@ package addr import ( "bytes" "embed" + "encoding/hex" "fmt" + "strings" + "math/big" + "github.com/ethereum/go-ethereum/crypto" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/utils/helpers" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -19,6 +26,7 @@ import ( const ( GetSeiAddressMethod = "getSeiAddr" GetEvmAddressMethod = "getEvmAddr" + Associate = "associate" ) const ( @@ -31,25 +39,23 @@ const ( var f embed.FS type PrecompileExecutor struct { - evmKeeper pcommon.EVMKeeper + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + accountKeeper pcommon.AccountKeeper GetSeiAddressID []byte GetEvmAddressID []byte + AssociateID []byte } -func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - return nil, fmt.Errorf("error loading the addr ABI %s", err) - } +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.Precompile, error) { - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - return nil, err - } + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ - evmKeeper: evmKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + accountKeeper: accountKeeper, } for name, m := range newAbi.Methods { @@ -58,6 +64,8 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) { p.GetSeiAddressID = m.ID case GetEvmAddressMethod: p.GetEvmAddressID = m.ID + case Associate: + p.AssociateID = m.ID } } @@ -66,6 +74,9 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) { // RequiredGas returns the required bare minimum gas to execute the precompile. func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + if bytes.Equal(method.ID, p.AssociateID) { + return 50000 + } return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) } @@ -75,6 +86,8 @@ func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, _ commo return p.getSeiAddr(ctx, method, args, value) case GetEvmAddressMethod: return p.getEvmAddr(ctx, method, args, value) + case Associate: + return p.associate(ctx, method, args, value) } return } @@ -118,6 +131,77 @@ func (p PrecompileExecutor) getEvmAddr(ctx sdk.Context, method *abi.Method, args return method.Outputs.Pack(evmAddr) } -func (PrecompileExecutor) IsTransaction(string) bool { - return false +func (p PrecompileExecutor) associate(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, err + } + + // v, r and s are components of a signature over the customMessage sent. + // We use the signature to construct the user's pubkey to obtain their addresses. + v := args[0].(string) + r := args[1].(string) + s := args[2].(string) + customMessage := args[3].(string) + + rBytes, err := decodeHexString(r) + if err != nil { + return nil, err + } + sBytes, err := decodeHexString(s) + if err != nil { + return nil, err + } + vBytes, err := decodeHexString(v) + if err != nil { + return nil, err + } + + vBig := new(big.Int).SetBytes(vBytes) + rBig := new(big.Int).SetBytes(rBytes) + sBig := new(big.Int).SetBytes(sBytes) + + // Derive addresses + vBig = new(big.Int).Add(vBig, utils.Big27) + + customMessageHash := crypto.Keccak256Hash([]byte(customMessage)) + evmAddr, seiAddr, pubkey, err := helpers.GetAddresses(vBig, rBig, sBig, customMessageHash) + if err != nil { + return nil, err + } + + // Check that address is not already associated + _, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if found { + return nil, fmt.Errorf("address %s is already associated with evm address %s", seiAddr, evmAddr) + } + + // Associate Addresses: + associationHelper := helpers.NewAssociationHelper(p.evmKeeper, p.bankKeeper, p.accountKeeper) + err = associationHelper.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(seiAddr.String(), evmAddr) +} + +func (PrecompileExecutor) IsTransaction(method string) bool { + switch method { + case Associate: + return true + default: + return false + } +} + +func decodeHexString(hexString string) ([]byte, error) { + trimmed := strings.TrimPrefix(hexString, "0x") + if len(trimmed)%2 != 0 { + trimmed = "0" + trimmed + } + return hex.DecodeString(trimmed) } diff --git a/precompiles/addr/addr_test.go b/precompiles/addr/addr_test.go new file mode 100644 index 000000000..06b84bf19 --- /dev/null +++ b/precompiles/addr/addr_test.go @@ -0,0 +1,189 @@ +package addr_test + +import ( + "encoding/hex" + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/sei-protocol/sei-chain/precompiles/addr" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/stretchr/testify/require" + tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" +) + +func TestAssociate(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + + pre, _ := addr.NewPrecompile(k, k.BankKeeper(), k.AccountKeeper()) + associate, err := pre.ABI.MethodById(pre.GetExecutor().(*addr.PrecompileExecutor).AssociateID) + + // Target refers to the address that the caller is trying to associate. + targetPrivKey := testkeeper.MockPrivateKey() + targetPrivHex := hex.EncodeToString(targetPrivKey.Bytes()) + targetSeiAddress, targetEvmAddress := testkeeper.PrivateKeyToAddresses(targetPrivKey) + targetKey, _ := crypto.HexToECDSA(targetPrivHex) + + // Create the inputs + emptyData := make([]byte, 32) + prefixedMessage := fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(emptyData)) + string(emptyData) + hash := crypto.Keccak256Hash([]byte(prefixedMessage)) + sig, err := crypto.Sign(hash.Bytes(), targetKey) + require.Nil(t, err) + + r := fmt.Sprintf("0x%v", new(big.Int).SetBytes(sig[:32]).Text(16)) + s := fmt.Sprintf("0x%v", new(big.Int).SetBytes(sig[32:64]).Text(16)) + v := fmt.Sprintf("0x%v", new(big.Int).SetBytes([]byte{sig[64]}).Text(16)) + + // Caller refers to the party calling the precompile. + callerPrivKey := testkeeper.MockPrivateKey() + callerSeiAddress, callerEvmAddress := testkeeper.PrivateKeyToAddresses(callerPrivKey) + callerPrivHex := hex.EncodeToString(callerPrivKey.Bytes()) + callerKey, _ := crypto.HexToECDSA(callerPrivHex) + + // Associate these addresses, so we can use them to test the case where addresses are already associated association. + k.SetAddressMapping(ctx, callerSeiAddress, callerEvmAddress) + callerSig, err := crypto.Sign(hash.Bytes(), callerKey) + callerR := fmt.Sprintf("0x%v", new(big.Int).SetBytes(callerSig[:32]).Text(16)) + callerS := fmt.Sprintf("0x%v", new(big.Int).SetBytes(callerSig[32:64]).Text(16)) + callerV := fmt.Sprintf("0x%v", new(big.Int).SetBytes([]byte{callerSig[64]}).Text(16)) + + happyPathOutput, _ := associate.Outputs.Pack(targetSeiAddress.String(), targetEvmAddress) + + type args struct { + evm *vm.EVM + caller common.Address + v string + r string + s string + msg string + value *big.Int + } + tests := []struct { + name string + args args + wantRet []byte + wantErr bool + wantErrMsg string + wrongRet bool + }{ + { + name: "fails if payable", + args: args{ + evm: &vm.EVM{ + StateDB: state.NewDBImpl(ctx, k, true), + TxContext: vm.TxContext{Origin: callerEvmAddress}, + }, + caller: callerEvmAddress, + v: v, + r: r, + s: s, + msg: prefixedMessage, + value: big.NewInt(10), + }, + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "fails if input is not hex", + args: args{ + evm: &vm.EVM{ + StateDB: state.NewDBImpl(ctx, k, true), + TxContext: vm.TxContext{Origin: callerEvmAddress}, + }, + caller: callerEvmAddress, + v: "nothex", + r: r, + s: s, + msg: prefixedMessage, + value: big.NewInt(0), + }, + wantErr: true, + wantErrMsg: "encoding/hex: invalid byte: U+006E 'n'", + }, + { + name: "fails if addresses are already associated", + args: args{ + evm: &vm.EVM{ + StateDB: state.NewDBImpl(ctx, k, true), + TxContext: vm.TxContext{Origin: callerEvmAddress}, + }, + caller: callerEvmAddress, + v: callerV, + r: callerR, + s: callerS, + msg: prefixedMessage, + value: big.NewInt(0), + }, + wantErr: true, + wantErrMsg: fmt.Sprintf("address %s is already associated with evm address %s", callerSeiAddress, callerEvmAddress), + }, + { + name: "associates wrong address if invalid signature (different message)", + args: args{ + evm: &vm.EVM{ + StateDB: state.NewDBImpl(ctx, k, true), + TxContext: vm.TxContext{Origin: callerEvmAddress}, + }, + caller: callerEvmAddress, + v: v, + r: r, + s: s, // Pass in r instead of s here for invalid value + msg: "Not the signed message", + value: big.NewInt(0), + }, + wantRet: happyPathOutput, + wrongRet: true, + }, + { + name: "happy path - associates addresses if signature is correct", + args: args{ + evm: &vm.EVM{ + StateDB: state.NewDBImpl(ctx, k, true), + TxContext: vm.TxContext{Origin: callerEvmAddress}, + }, + caller: callerEvmAddress, + v: v, + r: r, + s: s, + msg: prefixedMessage, + value: big.NewInt(0), + }, + wantRet: happyPathOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create the precompile and inputs + p, _ := addr.NewPrecompile(k, k.BankKeeper(), k.AccountKeeper()) + require.Nil(t, err) + inputs, err := associate.Inputs.Pack(tt.args.v, tt.args.r, tt.args.s, tt.args.msg) + require.Nil(t, err) + + // Make the call to associate. + ret, err := p.Run(tt.args.evm, tt.args.caller, tt.args.caller, append(p.GetExecutor().(*addr.PrecompileExecutor).AssociateID, inputs...), tt.args.value, false) + if (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v %v", err, tt.wantErr, string(ret)) + return + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Equal(t, tt.wantErrMsg, string(ret)) + } else if tt.wrongRet { + // tt.wrongRet is set if we expect a return value that's different from the happy path. This means that the wrong addresses were associated. + require.NotEqual(t, tt.wantRet, ret) + } else { + require.Equal(t, tt.wantRet, ret) + } + }) + } +} diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index 7cae74856..e8b361c36 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -1,7 +1,6 @@ package bank import ( - "bytes" "embed" "errors" "fmt" @@ -38,19 +37,6 @@ const ( //go:embed abi.json var f embed.FS -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - type PrecompileExecutor struct { accountKeeper pcommon.AccountKeeper bankKeeper pcommon.BankKeeper @@ -73,7 +59,7 @@ type CoinBalance struct { } func NewPrecompile(bankKeeper pcommon.BankKeeper, evmKeeper pcommon.EVMKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.Precompile, error) { - newAbi := GetABI() + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ bankKeeper: bankKeeper, evmKeeper: evmKeeper, diff --git a/precompiles/bank/bank_test.go b/precompiles/bank/bank_test.go index ad7a3dc88..2f086b6b2 100644 --- a/precompiles/bank/bank_test.go +++ b/precompiles/bank/bank_test.go @@ -1,6 +1,7 @@ package bank_test import ( + "embed" "encoding/hex" "fmt" "math/big" @@ -17,6 +18,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/sei-protocol/sei-chain/precompiles/bank" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/ante" "github.com/sei-protocol/sei-chain/x/evm/keeper" @@ -29,6 +31,9 @@ import ( tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" ) +//go:embed abi.json +var f embed.FS + type mockTx struct { msgs []sdk.Msg signers []sdk.AccAddress @@ -113,7 +118,7 @@ func TestRun(t *testing.T) { // Send native 10_000_000_000_100, split into 10 usei 100wei // Test payable with eth LegacyTx - abi := bank.GetABI() + abi := pcommon.MustGetABI(f, "abi.json") argsNative, err := abi.Pack(bank.SendNativeMethod, seiAddr.String()) require.Nil(t, err) require.Nil(t, err) diff --git a/precompiles/common/expected_keepers.go b/precompiles/common/expected_keepers.go index 292e53077..54a57019b 100644 --- a/precompiles/common/expected_keepers.go +++ b/precompiles/common/expected_keepers.go @@ -10,10 +10,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ibctypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/utils" oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" ) @@ -25,6 +28,8 @@ type BankKeeper interface { GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Int GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) GetSupply(ctx sdk.Context, denom string) sdk.Coin + LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins } type EVMKeeper interface { @@ -32,6 +37,7 @@ type EVMKeeper interface { GetSeiAddressOrDefault(ctx sdk.Context, evmAddress common.Address) sdk.AccAddress // only used for getting precompile Sei addresses GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) GetEVMAddressOrDefault(sdk.Context, sdk.AccAddress) common.Address + SetAddressMapping(sdk.Context, sdk.AccAddress, common.Address) GetCodeHash(sdk.Context, common.Address) common.Hash GetPriorityNormalizer(ctx sdk.Context) sdk.Dec GetBaseDenom(ctx sdk.Context) string @@ -41,11 +47,23 @@ type EVMKeeper interface { GetERC20CW20Pointer(ctx sdk.Context, cw20Address string) (addr common.Address, version uint16, exists bool) SetERC721CW721Pointer(ctx sdk.Context, cw721Address string, addr common.Address) error GetERC721CW721Pointer(ctx sdk.Context, cw721Address string) (addr common.Address, version uint16, exists bool) + SetCode(ctx sdk.Context, addr common.Address, code []byte) + UpsertERCNativePointer( + ctx sdk.Context, evm *vm.EVM, suppliedGas uint64, token string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, remainingGas uint64, err error) + UpsertERCCW20Pointer( + ctx sdk.Context, evm *vm.EVM, suppliedGas uint64, cw20Addr string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, remainingGas uint64, err error) + UpsertERCCW721Pointer( + ctx sdk.Context, evm *vm.EVM, suppliedGas uint64, cw721Addr string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, remainingGas uint64, err error) } type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI HasAccount(ctx sdk.Context, addr sdk.AccAddress) bool SetAccount(ctx sdk.Context, acc authtypes.AccountI) + RemoveAccount(ctx sdk.Context, acc authtypes.AccountI) NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI } @@ -69,6 +87,10 @@ type StakingKeeper interface { Undelegate(goCtx context.Context, msg *stakingtypes.MsgUndelegate) (*stakingtypes.MsgUndelegateResponse, error) } +type StakingQuerier interface { + Delegation(c context.Context, req *stakingtypes.QueryDelegationRequest) (*stakingtypes.QueryDelegationResponse, error) +} + type GovKeeper interface { AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, options govtypes.WeightedVoteOptions) error AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) @@ -77,6 +99,7 @@ type GovKeeper interface { type DistributionKeeper interface { SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) + DelegationTotalRewards(c context.Context, req *distrtypes.QueryDelegationTotalRewardsRequest) (*distrtypes.QueryDelegationTotalRewardsResponse, error) } type TransferKeeper interface { diff --git a/precompiles/common/precompiles.go b/precompiles/common/precompiles.go index b51da4dfe..0afa5c67b 100644 --- a/precompiles/common/precompiles.go +++ b/precompiles/common/precompiles.go @@ -1,6 +1,8 @@ package common import ( + "bytes" + "embed" "errors" "fmt" "math/big" @@ -14,6 +16,7 @@ import ( "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/utils/metrics" "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" ) const UnknownMethodCallGas uint64 = 3000 @@ -58,6 +61,10 @@ func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract comm operation := fmt.Sprintf("%s_unknown", p.name) defer func() { HandlePrecompileError(err, evm, operation) + if err != nil { + bz = []byte(err.Error()) + err = vm.ErrExecutionReverted + } }() ctx, method, args, err := p.Prepare(evm, input) if err != nil { @@ -144,6 +151,10 @@ func (d DynamicGasPrecompile) RunAndCalculateGas(evm *vm.EVM, caller common.Addr operation := fmt.Sprintf("%s_unknown", d.name) defer func() { HandlePrecompileError(err, evm, operation) + if err != nil { + ret = []byte(err.Error()) + err = vm.ErrExecutionReverted + } }() ctx, method, args, err := d.Prepare(evm, input) if err != nil { @@ -244,3 +255,32 @@ func DefaultGasCost(input []byte, isTransaction bool) uint64 { return storetypes.KVGasConfig().ReadCostFlat + (storetypes.KVGasConfig().ReadCostPerByte * uint64(len(input))) } + +func MustGetABI(f embed.FS, filename string) abi.ABI { + abiBz, err := f.ReadFile(filename) + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +func GetSeiAddressByEvmAddress(ctx sdk.Context, evmAddress common.Address, evmKeeper EVMKeeper) (sdk.AccAddress, error) { + seiAddr, associated := evmKeeper.GetSeiAddress(ctx, evmAddress) + if !associated { + return nil, types.NewAssociationMissingErr(evmAddress.Hex()) + } + return seiAddr, nil +} + +func GetSeiAddressFromArg(ctx sdk.Context, arg interface{}, evmKeeper EVMKeeper) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + return GetSeiAddressByEvmAddress(ctx, addr, evmKeeper) +} diff --git a/precompiles/common/precompiles_test.go b/precompiles/common/precompiles_test.go index 5edd4f59e..fab3d849f 100644 --- a/precompiles/common/precompiles_test.go +++ b/precompiles/common/precompiles_test.go @@ -82,7 +82,7 @@ func TestPrecompileRun(t *testing.T) { stateDB.WithCtx(ctx.WithEventManager(sdk.NewEventManager())) precompile = common.NewPrecompile(newAbi, &MockPrecompileExecutor{throw: true}, ethcommon.Address{}, "test") res, err = precompile.Run(&vm.EVM{StateDB: stateDB}, ethcommon.Address{}, ethcommon.Address{}, input, big.NewInt(0), false) - require.Nil(t, res) + require.NotNil(t, res) require.NotNil(t, err) // should not emit any event require.Empty(t, stateDB.Ctx().EventManager().Events()) @@ -122,7 +122,7 @@ func TestDynamicGasPrecompileRun(t *testing.T) { stateDB.WithCtx(ctx.WithEventManager(sdk.NewEventManager())) precompile = common.NewDynamicGasPrecompile(newAbi, &MockDynamicGasPrecompileExecutor{throw: true, evmKeeper: k}, ethcommon.Address{}, "test") res, _, err = precompile.RunAndCalculateGas(&vm.EVM{StateDB: stateDB}, ethcommon.Address{}, ethcommon.Address{}, input, 0, big.NewInt(0), nil, false) - require.Nil(t, res) + require.NotNil(t, res) require.NotNil(t, err) // should not emit any event require.Empty(t, stateDB.Ctx().EventManager().Events()) diff --git a/precompiles/distribution/Distribution.sol b/precompiles/distribution/Distribution.sol index aeb2d055b..057abdf9a 100644 --- a/precompiles/distribution/Distribution.sol +++ b/precompiles/distribution/Distribution.sol @@ -14,4 +14,23 @@ interface IDistr { function withdrawDelegationRewards(string memory validator) external returns (bool success); function withdrawMultipleDelegationRewards(string[] memory validators) external returns (bool success); + + // Queries + function rewards(address delegatorAddress) external view returns (Rewards rewards); + + struct Coin { + uint256 amount; + uint256 decimals; + string denom; + } + + struct Reward { + Coin[] coins; + string validator_address; + } + + struct Rewards { + Reward[] rewards; + Coin[] total; + } } diff --git a/precompiles/distribution/abi.json b/precompiles/distribution/abi.json index 826529c95..739390f50 100755 --- a/precompiles/distribution/abi.json +++ b/precompiles/distribution/abi.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string[]","name":"validators","type":"string[]"}],"name":"withdrawMultipleDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] +[{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string[]","name":"validators","type":"string[]"}],"name":"withdrawMultipleDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegatorAddress","type":"address"}],"name":"rewards","outputs":[{"components":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct Coin[]","name":"coins","type":"tuple[]"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct Reward[]","name":"rewards","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct Coin[]","name":"total","type":"tuple[]"}],"internalType":"struct Rewards","name":"rewards","type":"tuple"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index 0e8fde4fe..d24c63efc 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -1,12 +1,13 @@ package distribution import ( - "bytes" "embed" "errors" "fmt" "math/big" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -20,6 +21,7 @@ const ( SetWithdrawAddressMethod = "setWithdrawAddress" WithdrawDelegationRewardsMethod = "withdrawDelegationRewards" WithdrawMultipleDelegationRewardsMethod = "withdrawMultipleDelegationRewards" + RewardsMethod = "rewards" ) const ( @@ -31,19 +33,6 @@ const ( //go:embed abi.json var f embed.FS -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - type PrecompileExecutor struct { distrKeeper pcommon.DistributionKeeper evmKeeper pcommon.EVMKeeper @@ -52,10 +41,11 @@ type PrecompileExecutor struct { SetWithdrawAddrID []byte WithdrawDelegationRewardsID []byte WithdrawMultipleDelegationRewardsID []byte + RewardsID []byte } func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.DynamicGasPrecompile, error) { - newAbi := GetABI() + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ distrKeeper: distrKeeper, @@ -71,6 +61,8 @@ func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVM p.WithdrawDelegationRewardsID = m.ID case WithdrawMultipleDelegationRewardsMethod: p.WithdrawMultipleDelegationRewardsID = m.ID + case RewardsMethod: + p.RewardsID = m.ID } } @@ -78,14 +70,27 @@ func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVM } func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall distr") + } switch method.Name { case SetWithdrawAddressMethod: + if readOnly { + return nil, 0, errors.New("cannot call distr precompile from staticcall") + } return p.setWithdrawAddress(ctx, method, caller, args, value) case WithdrawDelegationRewardsMethod: + if readOnly { + return nil, 0, errors.New("cannot call distr precompile from staticcall") + } return p.withdrawDelegationRewards(ctx, method, caller, args, value) case WithdrawMultipleDelegationRewardsMethod: + if readOnly { + return nil, 0, errors.New("cannot call distr precompile from staticcall") + } return p.withdrawMultipleDelegationRewards(ctx, method, caller, args, value) - + case RewardsMethod: + return p.rewards(ctx, method, args) } return } @@ -94,10 +99,6 @@ func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { return p.evmKeeper } -func (p PrecompileExecutor) GetName() string { - return "distribution" -} - func (p PrecompileExecutor) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { defer func() { if err := recover(); err != nil { @@ -240,3 +241,89 @@ func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) } return seiAddr, nil } + +type Coin struct { + Amount *big.Int + Denom string + Decimals *big.Int +} + +type Reward struct { + ValidatorAddress string + Coins []Coin +} + +type Rewards struct { + Rewards []Reward + Total []Coin +} + +func (p PrecompileExecutor) rewards(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + seiDelegatorAddress, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + rerr = err + return + } + + req := &distrtypes.QueryDelegationTotalRewardsRequest{ + DelegatorAddress: seiDelegatorAddress.String(), + } + + wrappedC := sdk.WrapSDKContext(ctx) + response, err := p.distrKeeper.DelegationTotalRewards(wrappedC, req) + if err != nil { + rerr = err + return + } + + rewardsOutput := getResponseOutput(response) + ret, rerr = method.Outputs.Pack(rewardsOutput) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func getResponseOutput(response *distrtypes.QueryDelegationTotalRewardsResponse) Rewards { + rewards := make([]Reward, 0, len(response.Rewards)) + for _, rewardInfo := range response.Rewards { + coins := make([]Coin, 0, len(rewardInfo.Reward)) + for _, coin := range rewardInfo.Reward { + coins = append(coins, Coin{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + Decimals: big.NewInt(sdk.Precision), + }) + } + rewards = append(rewards, Reward{ + ValidatorAddress: rewardInfo.ValidatorAddress, + Coins: coins, + }) + } + + totalCoins := make([]Coin, 0, len(response.Total)) + for _, coin := range response.Total { + totalCoins = append(totalCoins, Coin{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + Decimals: big.NewInt(sdk.Precision), + }) + } + + return Rewards{ + Rewards: rewards, + Total: totalCoins, + } +} diff --git a/precompiles/distribution/distribution_test.go b/precompiles/distribution/distribution_test.go index d60dfcaf9..cc7f0c770 100644 --- a/precompiles/distribution/distribution_test.go +++ b/precompiles/distribution/distribution_test.go @@ -1,13 +1,16 @@ package distribution_test import ( + "context" + "embed" "encoding/hex" - "math/big" "reflect" "testing" "time" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" crptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -35,6 +38,10 @@ import ( tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" ) +//go:embed abi.json +//go:embed staking_abi.json +var f embed.FS + func TestWithdraw(t *testing.T) { testApp := testkeeper.EVMTestApp ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) @@ -46,7 +53,7 @@ func TestWithdraw(t *testing.T) { val := setupValidator(t, ctx, testApp, stakingtypes.Unbonded, valPub1) // delegate - abi := staking.GetABI() + abi := pcommon.MustGetABI(f, "staking_abi.json") args, err := abi.Pack("delegate", val.String()) require.Nil(t, err) @@ -94,7 +101,7 @@ func TestWithdraw(t *testing.T) { // set withdraw addr withdrawSeiAddr, withdrawAddr := testkeeper.MockAddressPair() k.SetAddressMapping(ctx, withdrawSeiAddr, withdrawAddr) - abi = distribution.GetABI() + abi = pcommon.MustGetABI(f, "abi.json") args, err = abi.Pack("setWithdrawAddress", withdrawAddr) require.Nil(t, err) addr = common.HexToAddress(distribution.DistrAddress) @@ -160,7 +167,7 @@ func TestWithdrawMultipleDelegationRewards(t *testing.T) { getValidator(t, ctx, testApp), getValidator(t, ctx, testApp)} - abi := staking.GetABI() + abi := pcommon.MustGetABI(f, "staking_abi.json") privKey := testkeeper.MockPrivateKey() addr := common.HexToAddress(staking.StakingAddress) chainID := k.ChainID(ctx) @@ -240,7 +247,7 @@ func setWithdrawAddressAndWithdraw( ) { withdrawSeiAddr, withdrawAddr := testkeeper.MockAddressPair() k.SetAddressMapping(ctx, withdrawSeiAddr, withdrawAddr) - abi := distribution.GetABI() + abi := pcommon.MustGetABI(f, "abi.json") args, err := abi.Pack("setWithdrawAddress", withdrawAddr) require.Nil(t, err) addr = common.HexToAddress(distribution.DistrAddress) @@ -346,6 +353,7 @@ func setupValidator(t *testing.T, ctx sdk.Context, a *app.App, bondStatus stakin func TestPrecompile_RunAndCalculateGas_WithdrawDelegationRewards(t *testing.T) { _, notAssociatedCallerEvmAddress := testkeeper.MockAddressPair() + _, contractEvmAddress := testkeeper.MockAddressPair() validatorAddress := "seivaloper1reedlc9w8p7jrpqfky4c5k90nea4p6dhk5yqgd" type fields struct { @@ -364,6 +372,7 @@ func TestPrecompile_RunAndCalculateGas_WithdrawDelegationRewards(t *testing.T) { validator string suppliedGas uint64 value *big.Int + readOnly bool } tests := []struct { name string @@ -402,9 +411,10 @@ func TestPrecompile_RunAndCalculateGas_WithdrawDelegationRewards(t *testing.T) { name: "fails if delegator is not associated", fields: fields{}, args: args{ - caller: notAssociatedCallerEvmAddress, - validator: validatorAddress, - suppliedGas: uint64(1000000), + caller: notAssociatedCallerEvmAddress, + callingContract: notAssociatedCallerEvmAddress, + validator: validatorAddress, + suppliedGas: uint64(1000000), }, wantRet: nil, wantRemainingGas: 0, @@ -420,6 +430,49 @@ func TestPrecompile_RunAndCalculateGas_WithdrawDelegationRewards(t *testing.T) { wantErr: true, wantErrMsg: "{ReadFlat}", }, + { + name: "fails if caller != callingContract", + fields: fields{}, + args: args{ + caller: notAssociatedCallerEvmAddress, + callingContract: contractEvmAddress, + validator: validatorAddress, + suppliedGas: uint64(1000000), + }, + wantRet: nil, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot delegatecall distr", + }, + { + name: "fails if caller != callingContract and callingContract not set", + fields: fields{}, + args: args{ + caller: notAssociatedCallerEvmAddress, + callingContract: contractEvmAddress, + validator: validatorAddress, + suppliedGas: uint64(1000000), + }, + wantRet: nil, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot delegatecall distr", + }, + { + name: "fails if readOnly", + fields: fields{}, + args: args{ + caller: notAssociatedCallerEvmAddress, + callingContract: notAssociatedCallerEvmAddress, + validator: validatorAddress, + suppliedGas: uint64(1000000), + readOnly: true, + }, + wantRet: nil, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot call distr precompile from staticcall", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -435,15 +488,15 @@ func TestPrecompile_RunAndCalculateGas_WithdrawDelegationRewards(t *testing.T) { require.Nil(t, err) inputs, err := withdraw.Inputs.Pack(tt.args.validator) require.Nil(t, err) - gotRet, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.GetExecutor().(*distribution.PrecompileExecutor).WithdrawDelegationRewardsID, inputs...), tt.args.suppliedGas, tt.args.value, nil, false) + gotRet, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.GetExecutor().(*distribution.PrecompileExecutor).WithdrawDelegationRewardsID, inputs...), tt.args.suppliedGas, tt.args.value, nil, tt.args.readOnly) if (err != nil) != tt.wantErr { t.Errorf("RunAndCalculateGas() error = %v, wantErr %v", err, tt.wantErr) return } if err != nil { - require.Equal(t, tt.wantErrMsg, err.Error()) - } - if !reflect.DeepEqual(gotRet, tt.wantRet) { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Equal(t, tt.wantErrMsg, string(gotRet)) + } else if !reflect.DeepEqual(gotRet, tt.wantRet) { t.Errorf("RunAndCalculateGas() gotRet = %v, want %v", gotRet, tt.wantRet) } if gotRemainingGas != tt.wantRemainingGas { @@ -455,6 +508,7 @@ func TestPrecompile_RunAndCalculateGas_WithdrawDelegationRewards(t *testing.T) { func TestPrecompile_RunAndCalculateGas_WithdrawMultipleDelegationRewards(t *testing.T) { _, notAssociatedCallerEvmAddress := testkeeper.MockAddressPair() + _, contractEvmAddress := testkeeper.MockAddressPair() validatorAddresses := []string{"seivaloper1reedlc9w8p7jrpqfky4c5k90nea4p6dhk5yqgd"} type fields struct { @@ -473,6 +527,7 @@ func TestPrecompile_RunAndCalculateGas_WithdrawMultipleDelegationRewards(t *test validators []string suppliedGas uint64 value *big.Int + readOnly bool } tests := []struct { name string @@ -511,9 +566,10 @@ func TestPrecompile_RunAndCalculateGas_WithdrawMultipleDelegationRewards(t *test name: "fails if delegator is not associated", fields: fields{}, args: args{ - caller: notAssociatedCallerEvmAddress, - validators: validatorAddresses, - suppliedGas: uint64(1000000), + caller: notAssociatedCallerEvmAddress, + callingContract: notAssociatedCallerEvmAddress, + validators: validatorAddresses, + suppliedGas: uint64(1000000), }, wantRet: nil, wantRemainingGas: 0, @@ -529,6 +585,48 @@ func TestPrecompile_RunAndCalculateGas_WithdrawMultipleDelegationRewards(t *test wantErr: true, wantErrMsg: "{ReadFlat}", }, + { + name: "fails if caller != callingContract", + fields: fields{}, + args: args{ + caller: notAssociatedCallerEvmAddress, + callingContract: contractEvmAddress, + validators: validatorAddresses, + suppliedGas: uint64(1000000), + }, + wantRet: nil, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot delegatecall distr", + }, + { + name: "fails if caller != callingContract and callingContract not set", + fields: fields{}, + args: args{ + caller: notAssociatedCallerEvmAddress, + validators: validatorAddresses, + suppliedGas: uint64(1000000), + }, + wantRet: nil, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot delegatecall distr", + }, + { + name: "fails if readOnly", + fields: fields{}, + args: args{ + caller: notAssociatedCallerEvmAddress, + callingContract: notAssociatedCallerEvmAddress, + validators: validatorAddresses, + suppliedGas: uint64(1000000), + readOnly: true, + }, + wantRet: nil, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot call distr precompile from staticcall", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -544,15 +642,15 @@ func TestPrecompile_RunAndCalculateGas_WithdrawMultipleDelegationRewards(t *test require.Nil(t, err) inputs, err := withdraw.Inputs.Pack(tt.args.validators) require.Nil(t, err) - gotRet, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.GetExecutor().(*distribution.PrecompileExecutor).WithdrawMultipleDelegationRewardsID, inputs...), tt.args.suppliedGas, tt.args.value, nil, false) + gotRet, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.GetExecutor().(*distribution.PrecompileExecutor).WithdrawMultipleDelegationRewardsID, inputs...), tt.args.suppliedGas, tt.args.value, nil, tt.args.readOnly) if (err != nil) != tt.wantErr { t.Errorf("RunAndCalculateGas() error = %v, wantErr %v", err, tt.wantErr) return } if err != nil { - require.Equal(t, tt.wantErrMsg, err.Error()) - } - if !reflect.DeepEqual(gotRet, tt.wantRet) { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Equal(t, tt.wantErrMsg, string(gotRet)) + } else if !reflect.DeepEqual(gotRet, tt.wantRet) { t.Errorf("RunAndCalculateGas() gotRet = %v, want %v", gotRet, tt.wantRet) } if gotRemainingGas != tt.wantRemainingGas { @@ -565,6 +663,7 @@ func TestPrecompile_RunAndCalculateGas_WithdrawMultipleDelegationRewards(t *test func TestPrecompile_RunAndCalculateGas_SetWithdrawAddress(t *testing.T) { _, notAssociatedCallerEvmAddress := testkeeper.MockAddressPair() callerSeiAddress, callerEvmAddress := testkeeper.MockAddressPair() + _, contractEvmAddress := testkeeper.MockAddressPair() type fields struct { Precompile pcommon.Precompile @@ -582,6 +681,7 @@ func TestPrecompile_RunAndCalculateGas_SetWithdrawAddress(t *testing.T) { callingContract common.Address suppliedGas uint64 value *big.Int + readOnly bool } tests := []struct { name string @@ -620,9 +720,10 @@ func TestPrecompile_RunAndCalculateGas_SetWithdrawAddress(t *testing.T) { name: "fails if delegator is not associated", fields: fields{}, args: args{ - addressToSet: notAssociatedCallerEvmAddress, - caller: notAssociatedCallerEvmAddress, - suppliedGas: uint64(1000000), + addressToSet: notAssociatedCallerEvmAddress, + caller: notAssociatedCallerEvmAddress, + callingContract: notAssociatedCallerEvmAddress, + suppliedGas: uint64(1000000), }, wantRet: nil, wantRemainingGas: 0, @@ -633,9 +734,10 @@ func TestPrecompile_RunAndCalculateGas_SetWithdrawAddress(t *testing.T) { name: "fails if address is invalid", fields: fields{}, args: args{ - addressToSet: common.Address{}, - caller: callerEvmAddress, - suppliedGas: uint64(1000000), + addressToSet: common.Address{}, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + suppliedGas: uint64(1000000), }, wantRet: nil, wantRemainingGas: 0, @@ -651,6 +753,48 @@ func TestPrecompile_RunAndCalculateGas_SetWithdrawAddress(t *testing.T) { wantErr: true, wantErrMsg: "{ReadFlat}", }, + { + name: "fails if caller != callingContract", + fields: fields{}, + args: args{ + addressToSet: common.Address{}, + caller: callerEvmAddress, + callingContract: contractEvmAddress, + suppliedGas: uint64(1000000), + }, + wantRet: nil, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot delegatecall distr", + }, + { + name: "fails if caller != callingContract with callingContract not set", + fields: fields{}, + args: args{ + addressToSet: common.Address{}, + caller: callerEvmAddress, + suppliedGas: uint64(1000000), + }, + wantRet: nil, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot delegatecall distr", + }, + { + name: "fails if readOnly", + fields: fields{}, + args: args{ + addressToSet: common.Address{}, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + suppliedGas: uint64(1000000), + readOnly: true, + }, + wantRet: nil, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot call distr precompile from staticcall", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -668,15 +812,290 @@ func TestPrecompile_RunAndCalculateGas_SetWithdrawAddress(t *testing.T) { require.Nil(t, err) inputs, err := setAddress.Inputs.Pack(tt.args.addressToSet) require.Nil(t, err) - gotRet, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.GetExecutor().(*distribution.PrecompileExecutor).SetWithdrawAddrID, inputs...), tt.args.suppliedGas, tt.args.value, nil, false) + gotRet, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.GetExecutor().(*distribution.PrecompileExecutor).SetWithdrawAddrID, inputs...), tt.args.suppliedGas, tt.args.value, nil, tt.args.readOnly) if (err != nil) != tt.wantErr { t.Errorf("RunAndCalculateGas() error = %v, wantErr %v", err, tt.wantErr) return } if err != nil { - require.Equal(t, tt.wantErrMsg, err.Error()) + require.Equal(t, vm.ErrExecutionReverted, err) + require.Equal(t, tt.wantErrMsg, string(gotRet)) + } else if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("RunAndCalculateGas() gotRet = %v, want %v", gotRet, tt.wantRet) } - if !reflect.DeepEqual(gotRet, tt.wantRet) { + if gotRemainingGas != tt.wantRemainingGas { + t.Errorf("RunAndCalculateGas() gotRemainingGas = %v, want %v", gotRemainingGas, tt.wantRemainingGas) + } + }) + } +} + +type TestDistributionKeeper struct{} + +func (tk *TestDistributionKeeper) SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error { + return nil +} + +func (tk *TestDistributionKeeper) WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) { + return nil, nil +} + +func (tk *TestDistributionKeeper) DelegationTotalRewards(ctx context.Context, req *distrtypes.QueryDelegationTotalRewardsRequest) (*distrtypes.QueryDelegationTotalRewardsResponse, error) { + uatomCoins := 1 + val1useiCoins := 5 + val2useiCoins := 7 + rewards := []distrtypes.DelegationDelegatorReward{ + { + ValidatorAddress: "seivaloper1wuj3xg3yrw4ryxn9vygwuz0necs4klj7j9nay6", + Reward: sdk.NewDecCoins( + sdk.NewDecCoin("uatom", sdk.NewInt(int64(uatomCoins))), + sdk.NewDecCoin("usei", sdk.NewInt(int64(val1useiCoins))), + ), + }, + { + ValidatorAddress: "seivaloper16znh8ktn33dwnxxc9q0jmxmjf6hsz4tl0s6vxh", + Reward: sdk.NewDecCoins(sdk.NewDecCoin("usei", sdk.NewInt(int64(val2useiCoins)))), + }, + } + + allDecCoins := sdk.NewDecCoins(sdk.NewDecCoin("uatom", sdk.NewInt(int64(uatomCoins))), + sdk.NewDecCoin("usei", sdk.NewInt(int64(val1useiCoins+val2useiCoins)))) + + return &distrtypes.QueryDelegationTotalRewardsResponse{Rewards: rewards, Total: allDecCoins}, nil +} + +type TestEmptyRewardsDistributionKeeper struct{} + +func (tk *TestEmptyRewardsDistributionKeeper) SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error { + return nil +} + +func (tk *TestEmptyRewardsDistributionKeeper) WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) { + return nil, nil +} + +func (tk *TestEmptyRewardsDistributionKeeper) DelegationTotalRewards(ctx context.Context, req *distrtypes.QueryDelegationTotalRewardsRequest) (*distrtypes.QueryDelegationTotalRewardsResponse, error) { + rewards := []distrtypes.DelegationDelegatorReward{} + allDecCoins := sdk.NewDecCoins() + + return &distrtypes.QueryDelegationTotalRewardsResponse{Rewards: rewards, Total: allDecCoins}, nil +} + +func TestPrecompile_RunAndCalculateGas_Rewards(t *testing.T) { + callerSeiAddress, callerEvmAddress := testkeeper.MockAddressPair() + _, notAssociatedCallerEvmAddress := testkeeper.MockAddressPair() + _, contractEvmAddress := testkeeper.MockAddressPair() + pre, _ := distribution.NewPrecompile(nil, nil) + rewardsMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*distribution.PrecompileExecutor).RewardsID) + coin1 := distribution.Coin{ + Amount: big.NewInt(1_000_000_000_000_000_000), + Denom: "uatom", + Decimals: big.NewInt(18), + } + coin2 := distribution.Coin{ + Amount: big.NewInt(5_000_000_000_000_000_000), + Denom: "usei", + Decimals: big.NewInt(18), + } + + coin3 := distribution.Coin{ + Amount: big.NewInt(7_000_000_000_000_000_000), + Denom: "usei", + Decimals: big.NewInt(18), + } + coinsVal1 := []distribution.Coin{coin1, coin2} + coinsVal2 := []distribution.Coin{coin3} + rewardVal1 := distribution.Reward{ + ValidatorAddress: "seivaloper1wuj3xg3yrw4ryxn9vygwuz0necs4klj7j9nay6", + Coins: coinsVal1, + } + rewardVal2 := distribution.Reward{ + ValidatorAddress: "seivaloper16znh8ktn33dwnxxc9q0jmxmjf6hsz4tl0s6vxh", + Coins: coinsVal2, + } + rewards := []distribution.Reward{rewardVal1, rewardVal2} + coin2Amount, _ := new(big.Int).SetString("12000000000000000000", 10) + totalCoins := []distribution.Coin{ + { + Amount: big.NewInt(1_000_000_000_000_000_000), + Denom: "uatom", + Decimals: big.NewInt(18), + }, + { + Amount: coin2Amount, + Denom: "usei", + Decimals: big.NewInt(18), + }, + } + rewardsOutput := distribution.Rewards{ + Rewards: rewards, + Total: totalCoins, + } + + happyPathPackedOutput, _ := rewardsMethod.Outputs.Pack(rewardsOutput) + emptyCasePackedOutput, _ := rewardsMethod.Outputs.Pack(distribution.Rewards{ + Rewards: []distribution.Reward{}, + Total: []distribution.Coin{}, + }) + type fields struct { + Precompile pcommon.Precompile + distrKeeper pcommon.DistributionKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + SetWithdrawAddrID []byte + WithdrawDelegationRewardsID []byte + WithdrawMultipleDelegationRewardsID []byte + } + type args struct { + evm *vm.EVM + delegatorAddress common.Address + caller common.Address + callingContract common.Address + suppliedGas uint64 + value *big.Int + readOnly bool + } + tests := []struct { + name string + fields fields + args args + wantRet []byte + wantRemainingGas uint64 + wantErr bool + wantErrMsg string + }{ + { + name: "fails if delegator is not passed", + fields: fields{ + distrKeeper: &TestDistributionKeeper{}, + }, + args: args{ + caller: callerEvmAddress, + callingContract: callerEvmAddress, + suppliedGas: uint64(1000000), + }, + wantRet: nil, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "invalid addr", + }, + { + name: "fails if delegator not associated", + fields: fields{ + distrKeeper: &TestDistributionKeeper{}, + }, + args: args{ + delegatorAddress: notAssociatedCallerEvmAddress, + caller: notAssociatedCallerEvmAddress, + callingContract: notAssociatedCallerEvmAddress, + suppliedGas: uint64(1000000), + }, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot use an unassociated address as withdraw address", + }, + { + name: "fails if delegator address is invalid", + fields: fields{ + distrKeeper: &TestDistributionKeeper{}, + }, + args: args{ + delegatorAddress: common.Address{}, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + suppliedGas: uint64(1000000), + }, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "invalid addr", + }, + { + name: "fails if caller != callingContract", + fields: fields{ + distrKeeper: &TestDistributionKeeper{}, + }, + args: args{ + delegatorAddress: common.Address{}, + caller: callerEvmAddress, + callingContract: contractEvmAddress, + suppliedGas: uint64(1000000), + }, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot delegatecall distr", + }, + { + name: "fails if caller != callingContract with callingContract not set", + fields: fields{ + distrKeeper: &TestDistributionKeeper{}, + }, + args: args{ + delegatorAddress: common.Address{}, + caller: callerEvmAddress, + suppliedGas: uint64(1000000), + }, + wantRemainingGas: 0, + wantErr: true, + wantErrMsg: "cannot delegatecall distr", + }, + { + name: "should return empty delegator rewards if no rewards", + fields: fields{ + distrKeeper: &TestEmptyRewardsDistributionKeeper{}, + }, + args: args{ + delegatorAddress: callerEvmAddress, + readOnly: true, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + suppliedGas: uint64(1000000), + }, + wantRet: emptyCasePackedOutput, + wantRemainingGas: 994319, + wantErr: false, + }, + { + name: "should return delegator rewards", + fields: fields{ + distrKeeper: &TestDistributionKeeper{}, + }, + args: args{ + delegatorAddress: callerEvmAddress, + readOnly: true, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + suppliedGas: uint64(1000000), + }, + wantRet: happyPathPackedOutput, + wantRemainingGas: 994319, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + k.SetAddressMapping(ctx, callerSeiAddress, callerEvmAddress) + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + TxContext: vm.TxContext{Origin: callerEvmAddress}, + } + p, _ := distribution.NewPrecompile(tt.fields.distrKeeper, k) + rewards, err := p.ABI.MethodById(p.GetExecutor().(*distribution.PrecompileExecutor).RewardsID) + require.Nil(t, err) + inputs, err := rewards.Inputs.Pack(tt.args.delegatorAddress) + require.Nil(t, err) + gotRet, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.GetExecutor().(*distribution.PrecompileExecutor).RewardsID, inputs...), tt.args.suppliedGas, tt.args.value, nil, tt.args.readOnly) + if (err != nil) != tt.wantErr { + t.Errorf("RunAndCalculateGas() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Equal(t, tt.wantErrMsg, string(gotRet)) + } else if !reflect.DeepEqual(gotRet, tt.wantRet) { t.Errorf("RunAndCalculateGas() gotRet = %v, want %v", gotRet, tt.wantRet) } if gotRemainingGas != tt.wantRemainingGas { diff --git a/precompiles/distribution/staking_abi.json b/precompiles/distribution/staking_abi.json new file mode 100755 index 000000000..0be519e4d --- /dev/null +++ b/precompiles/distribution/staking_abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"srcAddress","type":"string"},{"internalType":"string","name":"dstAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"redelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"valAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index 802bcd3e1..e3be07b76 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -29,19 +29,6 @@ const ( //go:embed abi.json var f embed.FS -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - type PrecompileExecutor struct { govKeeper pcommon.GovKeeper evmKeeper pcommon.EVMKeeper @@ -53,7 +40,7 @@ type PrecompileExecutor struct { } func NewPrecompile(govKeeper pcommon.GovKeeper, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.Precompile, error) { - newAbi := GetABI() + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ govKeeper: govKeeper, diff --git a/precompiles/gov/gov_test.go b/precompiles/gov/gov_test.go index 43cca676b..2c44fa81f 100644 --- a/precompiles/gov/gov_test.go +++ b/precompiles/gov/gov_test.go @@ -1,6 +1,7 @@ package gov_test import ( + "embed" "encoding/hex" "math/big" "testing" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/require" tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" "github.com/sei-protocol/sei-chain/precompiles/gov" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/ante" @@ -21,6 +23,9 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/types/ethtx" ) +//go:embed abi.json +var f embed.FS + func TestGovPrecompile(t *testing.T) { testApp := testkeeper.EVMTestApp ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) @@ -34,7 +39,7 @@ func TestGovPrecompile(t *testing.T) { testApp.GovKeeper.ActivateVotingPeriod(ctx, proposal2) k := &testApp.EvmKeeper - abi := gov.GetABI() + abi := pcommon.MustGetABI(f, "abi.json") type args struct { method string diff --git a/precompiles/ibc/ibc.go b/precompiles/ibc/ibc.go index f353b39f1..160ab8cdb 100644 --- a/precompiles/ibc/ibc.go +++ b/precompiles/ibc/ibc.go @@ -1,7 +1,6 @@ package ibc import ( - "bytes" "embed" "errors" "fmt" @@ -34,19 +33,6 @@ const ( //go:embed abi.json var f embed.FS -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - type PrecompileExecutor struct { transferKeeper pcommon.TransferKeeper evmKeeper pcommon.EVMKeeper @@ -64,7 +50,7 @@ func NewPrecompile( clientKeeper pcommon.ClientKeeper, connectionKeeper pcommon.ConnectionKeeper, channelKeeper pcommon.ChannelKeeper) (*pcommon.DynamicGasPrecompile, error) { - newAbi := GetABI() + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ transferKeeper: transferKeeper, diff --git a/precompiles/ibc/ibc_test.go b/precompiles/ibc/ibc_test.go index ac20bd27a..da56d0f93 100644 --- a/precompiles/ibc/ibc_test.go +++ b/precompiles/ibc/ibc_test.go @@ -284,12 +284,12 @@ func TestPrecompile_Run(t *testing.T) { return } if err != nil { - require.Equal(t, tt.wantErrMsg, err.Error()) + require.Equal(t, vm.ErrExecutionReverted, err) + require.Equal(t, tt.wantErrMsg, string(gotBz)) + } else if !reflect.DeepEqual(gotBz, tt.wantBz) { + t.Errorf("Run() gotRet = %v, want %v", gotBz, tt.wantBz) } - if !reflect.DeepEqual(gotBz, tt.wantBz) { - t.Errorf("Run() gotBz = %v, want %v", gotBz, tt.wantBz) - } if !reflect.DeepEqual(gotRemainingGas, tt.wantRemainingGas) { t.Errorf("Run() gotRemainingGas = %v, want %v", gotRemainingGas, tt.wantRemainingGas) } @@ -471,11 +471,10 @@ func TestTransferWithDefaultTimeoutPrecompile_Run(t *testing.T) { return } if err != nil { - require.Equal(t, tt.wantErrMsg, err.Error()) - } - - if !reflect.DeepEqual(gotBz, tt.wantBz) { - t.Errorf("Run() gotBz = %v, want %v", gotBz, tt.wantBz) + require.Equal(t, vm.ErrExecutionReverted, err) + require.Equal(t, tt.wantErrMsg, string(gotBz)) + } else if !reflect.DeepEqual(gotBz, tt.wantBz) { + t.Errorf("Run() gotRet = %v, want %v", gotBz, tt.wantBz) } if !reflect.DeepEqual(gotRemainingGas, tt.wantRemainingGas) { t.Errorf("Run() gotRemainingGas = %v, want %v", gotRemainingGas, tt.wantRemainingGas) diff --git a/precompiles/json/json.go b/precompiles/json/json.go index eceab916b..2536eb684 100644 --- a/precompiles/json/json.go +++ b/precompiles/json/json.go @@ -1,7 +1,6 @@ package json import ( - "bytes" "embed" gjson "encoding/json" "errors" @@ -37,24 +36,8 @@ type PrecompileExecutor struct { ExtractAsUint256ID []byte } -func ABI() (*abi.ABI, error) { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - return nil, fmt.Errorf("error loading the json ABI %s", err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - return nil, err - } - return &newAbi, nil -} - func NewPrecompile() (*pcommon.Precompile, error) { - newAbi, err := ABI() - if err != nil { - return nil, err - } + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{} @@ -69,7 +52,7 @@ func NewPrecompile() (*pcommon.Precompile, error) { } } - return pcommon.NewPrecompile(*newAbi, p, common.HexToAddress(JSONAddress), "json"), nil + return pcommon.NewPrecompile(newAbi, p, common.HexToAddress(JSONAddress), "json"), nil } // RequiredGas returns the required bare minimum gas to execute the precompile. diff --git a/precompiles/oracle/oracle.go b/precompiles/oracle/oracle.go index 663f992a1..548fc063e 100644 --- a/precompiles/oracle/oracle.go +++ b/precompiles/oracle/oracle.go @@ -1,7 +1,6 @@ package oracle import ( - "bytes" "embed" "math/big" @@ -28,19 +27,6 @@ const ( //go:embed abi.json var f embed.FS -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - type PrecompileExecutor struct { evmKeeper pcommon.EVMKeeper oracleKeeper pcommon.OracleKeeper @@ -68,7 +54,7 @@ type OracleTwap struct { } func NewPrecompile(oracleKeeper pcommon.OracleKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) { - newAbi := GetABI() + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ evmKeeper: evmKeeper, diff --git a/precompiles/pointer/pointer.go b/precompiles/pointer/pointer.go index dcbe5d722..8efe8386f 100644 --- a/precompiles/pointer/pointer.go +++ b/precompiles/pointer/pointer.go @@ -1,7 +1,6 @@ package pointer import ( - "bytes" "embed" "encoding/json" "errors" @@ -15,10 +14,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" pcommon "github.com/sei-protocol/sei-chain/precompiles/common" "github.com/sei-protocol/sei-chain/utils" - "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" - "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw721" - "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" - "github.com/sei-protocol/sei-chain/x/evm/types" ) const ( @@ -45,24 +40,8 @@ type PrecompileExecutor struct { AddCW721PointerID []byte } -func ABI() (*ethabi.ABI, error) { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - return nil, fmt.Errorf("error loading the pointer ABI %s", err) - } - - newAbi, err := ethabi.JSON(bytes.NewReader(abiBz)) - if err != nil { - return nil, err - } - return &newAbi, nil -} - func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, wasmdKeeper pcommon.WasmdViewKeeper) (*pcommon.DynamicGasPrecompile, error) { - newAbi, err := ABI() - if err != nil { - return nil, err - } + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ evmKeeper: evmKeeper, @@ -81,7 +60,7 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, w } } - return pcommon.NewDynamicGasPrecompile(*newAbi, p, common.HexToAddress(PointerAddress), PrecompileName), nil + return pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(PointerAddress), PrecompileName), nil } func (p PrecompileExecutor) Execute(ctx sdk.Context, method *ethabi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { @@ -117,10 +96,6 @@ func (p PrecompileExecutor) AddNative(ctx sdk.Context, method *ethabi.Method, ca return nil, 0, err } token := args[0].(string) - existingAddr, existingVersion, exists := p.evmKeeper.GetERC20NativePointer(ctx, token) - if exists && existingVersion >= native.CurrentVersion { - return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, native.CurrentVersion) - } metadata, metadataExists := p.bankKeeper.GetDenomMetaData(ctx, token) if !metadataExists { return nil, 0, fmt.Errorf("denom %s does not have metadata stored and thus can only have its pointer set through gov proposal", token) @@ -138,30 +113,10 @@ func (p PrecompileExecutor) AddNative(ctx sdk.Context, method *ethabi.Method, ca } } } - constructorArguments := []interface{}{ - token, name, symbol, decimals, - } - - packedArgs, err := native.GetParsedABI().Pack("", constructorArguments...) + contractAddr, remainingGas, err := p.evmKeeper.UpsertERCNativePointer(ctx, evm, suppliedGas, token, utils.ERCMetadata{Name: name, Symbol: symbol, Decimals: decimals}) if err != nil { - panic(err) - } - bin := append(native.GetBin(), packedArgs...) - if value == nil { - value = utils.Big0 - } - ret, contractAddr, remainingGas, err := evm.Create(vm.AccountRef(caller), bin, suppliedGas, value) - if err != nil { - return - } - err = p.evmKeeper.SetERC20NativePointer(ctx, token, contractAddr) - if err != nil { - return + return nil, 0, err } - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "native"), - sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, token), - sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", native.CurrentVersion)))) ret, err = method.Outputs.Pack(contractAddr) return } @@ -174,10 +129,6 @@ func (p PrecompileExecutor) AddCW20(ctx sdk.Context, method *ethabi.Method, call return nil, 0, err } cwAddr := args[0].(string) - existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, cwAddr) - if exists && existingVersion >= cw20.CurrentVersion(ctx) { - return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, cw20.CurrentVersion(ctx)) - } cwAddress, err := sdk.AccAddressFromBech32(cwAddr) if err != nil { return nil, 0, err @@ -192,30 +143,10 @@ func (p PrecompileExecutor) AddCW20(ctx sdk.Context, method *ethabi.Method, call } name := formattedRes["name"].(string) symbol := formattedRes["symbol"].(string) - constructorArguments := []interface{}{ - cwAddr, name, symbol, - } - - packedArgs, err := cw20.GetParsedABI().Pack("", constructorArguments...) + contractAddr, remainingGas, err := p.evmKeeper.UpsertERCCW20Pointer(ctx, evm, suppliedGas, cwAddr, utils.ERCMetadata{Name: name, Symbol: symbol}) if err != nil { - panic(err) - } - bin := append(cw20.GetBin(), packedArgs...) - if value == nil { - value = utils.Big0 - } - ret, contractAddr, remainingGas, err := evm.Create(vm.AccountRef(caller), bin, suppliedGas, value) - if err != nil { - return - } - err = p.evmKeeper.SetERC20CW20Pointer(ctx, cwAddr, contractAddr) - if err != nil { - return + return nil, 0, err } - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw20"), - sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), - sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw20.CurrentVersion(ctx))))) ret, err = method.Outputs.Pack(contractAddr) return } @@ -228,10 +159,6 @@ func (p PrecompileExecutor) AddCW721(ctx sdk.Context, method *ethabi.Method, cal return nil, 0, err } cwAddr := args[0].(string) - existingAddr, existingVersion, exists := p.evmKeeper.GetERC721CW721Pointer(ctx, cwAddr) - if exists && existingVersion >= cw721.CurrentVersion { - return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, cw721.CurrentVersion) - } cwAddress, err := sdk.AccAddressFromBech32(cwAddr) if err != nil { return nil, 0, err @@ -246,30 +173,10 @@ func (p PrecompileExecutor) AddCW721(ctx sdk.Context, method *ethabi.Method, cal } name := formattedRes["name"].(string) symbol := formattedRes["symbol"].(string) - constructorArguments := []interface{}{ - cwAddr, name, symbol, - } - - packedArgs, err := cw721.GetParsedABI().Pack("", constructorArguments...) + contractAddr, remainingGas, err := p.evmKeeper.UpsertERCCW721Pointer(ctx, evm, suppliedGas, cwAddr, utils.ERCMetadata{Name: name, Symbol: symbol}) if err != nil { - panic(err) - } - bin := append(cw721.GetBin(), packedArgs...) - if value == nil { - value = utils.Big0 - } - ret, contractAddr, remainingGas, err := evm.Create(vm.AccountRef(caller), bin, suppliedGas, value) - if err != nil { - return - } - err = p.evmKeeper.SetERC721CW721Pointer(ctx, cwAddr, contractAddr) - if err != nil { - return + return nil, 0, err } - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw721"), - sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), - sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw721.CurrentVersion)))) ret, err = method.Outputs.Pack(contractAddr) return } diff --git a/precompiles/pointer/pointer_test.go b/precompiles/pointer/pointer_test.go index a832224d8..e75c09f7f 100644 --- a/precompiles/pointer/pointer_test.go +++ b/precompiles/pointer/pointer_test.go @@ -75,11 +75,17 @@ func TestAddNative(t *testing.T) { } require.True(t, hasRegisteredEvent) - // pointer already exists + // upgrade to a newer version + // hacky way to get the existing version number to be below CurrentVersion + testApp.EvmKeeper.DeleteERC20NativePointer(statedb.Ctx(), "test", version) + testApp.EvmKeeper.SetERC20NativePointerWithVersion(statedb.Ctx(), "test", pointerAddr, version-1) statedb = state.NewDBImpl(statedb.Ctx(), &testApp.EvmKeeper, true) evm = vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) - _, g, err = p.RunAndCalculateGas(evm, caller, caller, append(p.GetExecutor().(*pointer.PrecompileExecutor).AddNativePointerID, args...), suppliedGas, nil, nil, false) - require.NotNil(t, err) - require.NotNil(t, statedb.GetPrecompileError()) - require.Equal(t, uint64(0), g) + _, _, err = p.RunAndCalculateGas(evm, caller, caller, append(p.GetExecutor().(*pointer.PrecompileExecutor).AddNativePointerID, args...), suppliedGas, nil, nil, false) + require.Nil(t, err) + require.Nil(t, statedb.GetPrecompileError()) + newAddr, _, exists := testApp.EvmKeeper.GetERC20NativePointer(statedb.Ctx(), "test") + require.True(t, exists) + require.Equal(t, addr, pointerAddr) + require.Equal(t, newAddr, pointerAddr) // address should stay the same as before } diff --git a/precompiles/pointerview/pointerview.go b/precompiles/pointerview/pointerview.go index 912c6ac94..4a55c204a 100644 --- a/precompiles/pointerview/pointerview.go +++ b/precompiles/pointerview/pointerview.go @@ -1,7 +1,6 @@ package pointerview import ( - "bytes" "embed" "fmt" "math/big" @@ -35,15 +34,7 @@ type PrecompileExecutor struct { } func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - return nil, fmt.Errorf("error loading the pointer ABI %s", err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - return nil, err - } + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ evmKeeper: evmKeeper, @@ -69,6 +60,9 @@ func (p PrecompileExecutor) RequiredGas([]byte, *abi.Method) uint64 { } func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (ret []byte, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } switch method.Name { case GetNativePointer: return p.GetNative(ctx, method, args) diff --git a/precompiles/setup.go b/precompiles/setup.go index 4eb193741..56c6ce3e4 100644 --- a/precompiles/setup.go +++ b/precompiles/setup.go @@ -45,6 +45,7 @@ func InitializePrecompiles( wasmdKeeper common.WasmdKeeper, wasmdViewKeeper common.WasmdViewKeeper, stakingKeeper common.StakingKeeper, + stakingQuerier common.StakingQuerier, govKeeper common.GovKeeper, distrKeeper common.DistributionKeeper, oracleKeeper common.OracleKeeper, @@ -71,11 +72,11 @@ func InitializePrecompiles( if err != nil { return err } - addrp, err := addr.NewPrecompile(evmKeeper) + addrp, err := addr.NewPrecompile(evmKeeper, bankKeeper, accountKeeper) if err != nil { return err } - stakingp, err := staking.NewPrecompile(stakingKeeper, evmKeeper, bankKeeper) + stakingp, err := staking.NewPrecompile(stakingKeeper, stakingQuerier, evmKeeper, bankKeeper) if err != nil { return err } @@ -134,7 +135,7 @@ func InitializePrecompiles( func GetPrecompileInfo(name string) PrecompileInfo { if !Initialized { // Precompile Info does not require any keeper state - _ = InitializePrecompiles(true, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) + _ = InitializePrecompiles(true, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) } i, ok := PrecompileNamesToInfo[name] if !ok { diff --git a/precompiles/staking/Staking.sol b/precompiles/staking/Staking.sol index b061ea18d..999209812 100644 --- a/precompiles/staking/Staking.sol +++ b/precompiles/staking/Staking.sol @@ -23,4 +23,27 @@ interface IStaking { string memory valAddress, uint256 amount ) external returns (bool success); + + // Queries + function delegation( + address delegator, + string memory valAddress + ) external view returns (Delegation delegation); + + struct Delegation { + Balance balance; + DelegationDetails delegation; + } + + struct Balance { + uint256 amount; + string denom; + } + + struct DelegationDetails { + string delegator_address; + uint256 shares; + uint256 decimals; + string validator_address; + } } \ No newline at end of file diff --git a/precompiles/staking/abi.json b/precompiles/staking/abi.json index 0be519e4d..bec7f5df4 100755 --- a/precompiles/staking/abi.json +++ b/precompiles/staking/abi.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"srcAddress","type":"string"},{"internalType":"string","name":"dstAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"redelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"valAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"srcAddress","type":"string"},{"internalType":"string","name":"dstAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"redelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"valAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegation","outputs":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct Balance","name":"balance","type":"tuple"},{"components":[{"internalType":"string","name":"delegator_address","type":"string"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct DelegationDetails","name":"delegation","type":"tuple"}],"internalType":"struct Delegation","name":"delegation","type":"tuple"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 4701d238a..443d4fef8 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -19,6 +19,7 @@ const ( DelegateMethod = "delegate" RedelegateMethod = "redelegate" UndelegateMethod = "undelegate" + DelegationMethod = "delegation" ) const ( @@ -30,38 +31,28 @@ const ( //go:embed abi.json var f embed.FS -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - type PrecompileExecutor struct { - stakingKeeper pcommon.StakingKeeper - evmKeeper pcommon.EVMKeeper - bankKeeper pcommon.BankKeeper - address common.Address + stakingKeeper pcommon.StakingKeeper + stakingQuerier pcommon.StakingQuerier + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + address common.Address DelegateID []byte RedelegateID []byte UndelegateID []byte + DelegationID []byte } -func NewPrecompile(stakingKeeper pcommon.StakingKeeper, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.Precompile, error) { - newAbi := GetABI() +func NewPrecompile(stakingKeeper pcommon.StakingKeeper, stakingQuerier pcommon.StakingQuerier, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.Precompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ - stakingKeeper: stakingKeeper, - evmKeeper: evmKeeper, - bankKeeper: bankKeeper, - address: common.HexToAddress(StakingAddress), + stakingKeeper: stakingKeeper, + stakingQuerier: stakingQuerier, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(StakingAddress), } for name, m := range newAbi.Methods { @@ -72,6 +63,8 @@ func NewPrecompile(stakingKeeper pcommon.StakingKeeper, evmKeeper pcommon.EVMKee p.RedelegateID = m.ID case UndelegateMethod: p.UndelegateID = m.ID + case DelegationMethod: + p.DelegationID = m.ID } } @@ -93,20 +86,27 @@ func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 } func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { - if readOnly { - return nil, errors.New("cannot call staking precompile from staticcall") - } if caller.Cmp(callingContract) != 0 { return nil, errors.New("cannot delegatecall staking") } - switch method.Name { case DelegateMethod: + if readOnly { + return nil, errors.New("cannot call staking precompile from staticcall") + } return p.delegate(ctx, method, caller, args, value) case RedelegateMethod: + if readOnly { + return nil, errors.New("cannot call staking precompile from staticcall") + } return p.redelegate(ctx, method, caller, args, value) case UndelegateMethod: + if readOnly { + return nil, errors.New("cannot call staking precompile from staticcall") + } return p.undelegate(ctx, method, caller, args, value) + case DelegationMethod: + return p.delegation(ctx, method, args, value) } return } @@ -192,3 +192,61 @@ func (p PrecompileExecutor) undelegate(ctx sdk.Context, method *abi.Method, call } return method.Outputs.Pack(true) } + +type Delegation struct { + Balance Balance + Delegation DelegationDetails +} + +type Balance struct { + Amount *big.Int + Denom string +} + +type DelegationDetails struct { + DelegatorAddress string + Shares *big.Int + Decimals *big.Int + ValidatorAddress string +} + +func (p PrecompileExecutor) delegation(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + seiDelegatorAddress, err := pcommon.GetSeiAddressFromArg(ctx, args[0], p.evmKeeper) + if err != nil { + return nil, err + } + + validatorBech32 := args[1].(string) + delegationRequest := &stakingtypes.QueryDelegationRequest{ + DelegatorAddr: seiDelegatorAddress.String(), + ValidatorAddr: validatorBech32, + } + + delegationResponse, err := p.stakingQuerier.Delegation(sdk.WrapSDKContext(ctx), delegationRequest) + if err != nil { + return nil, err + } + + delegation := Delegation{ + Balance: Balance{ + Amount: delegationResponse.GetDelegationResponse().GetBalance().Amount.BigInt(), + Denom: delegationResponse.GetDelegationResponse().GetBalance().Denom, + }, + Delegation: DelegationDetails{ + DelegatorAddress: delegationResponse.GetDelegationResponse().GetDelegation().DelegatorAddress, + Shares: delegationResponse.GetDelegationResponse().GetDelegation().Shares.BigInt(), + Decimals: big.NewInt(sdk.Precision), + ValidatorAddress: delegationResponse.GetDelegationResponse().GetDelegation().ValidatorAddress, + }, + } + + return method.Outputs.Pack(delegation) +} diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index 682dbcf2b..81eeae979 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -1,8 +1,12 @@ package staking_test import ( + "context" + "embed" "encoding/hex" + "fmt" "math/big" + "reflect" "testing" "time" @@ -14,12 +18,15 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/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/sei-protocol/sei-chain/app" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" "github.com/sei-protocol/sei-chain/precompiles/staking" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/ante" "github.com/sei-protocol/sei-chain/x/evm/keeper" + "github.com/sei-protocol/sei-chain/x/evm/state" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/sei-protocol/sei-chain/x/evm/types/ethtx" minttypes "github.com/sei-protocol/sei-chain/x/mint/types" @@ -27,6 +34,9 @@ import ( tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" ) +//go:embed abi.json +var f embed.FS + func TestStaking(t *testing.T) { testApp := testkeeper.EVMTestApp ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) @@ -37,7 +47,7 @@ func TestStaking(t *testing.T) { val2 := setupValidator(t, ctx, testApp, stakingtypes.Unbonded, valPub2) // delegate - abi := staking.GetABI() + abi := pcommon.MustGetABI(f, "abi.json") args, err := abi.Pack("delegate", val.String()) require.Nil(t, err) @@ -146,7 +156,7 @@ func TestStakingError(t *testing.T) { val := setupValidator(t, ctx, testApp, stakingtypes.Unbonded, valPub1) val2 := setupValidator(t, ctx, testApp, stakingtypes.Unbonded, valPub2) - abi := staking.GetABI() + abi := pcommon.MustGetABI(f, "abi.json") args, err := abi.Pack("undelegate", val.String(), big.NewInt(100)) require.Nil(t, err) @@ -246,3 +256,217 @@ func setupValidator(t *testing.T, ctx sdk.Context, a *app.App, bondStatus stakin return valAddr } + +type TestStakingQuerier struct { + Response *stakingtypes.QueryDelegationResponse + Err error +} + +func (tq *TestStakingQuerier) Delegation(c context.Context, _ *stakingtypes.QueryDelegationRequest) (*stakingtypes.QueryDelegationResponse, error) { + return tq.Response, tq.Err +} + +func TestPrecompile_Run_Delegation(t *testing.T) { + callerSeiAddress, callerEvmAddress := testkeeper.MockAddressPair() + _, unassociatedEvmAddress := testkeeper.MockAddressPair() + _, contractEvmAddress := testkeeper.MockAddressPair() + validatorAddress := "seivaloper134ykhqrkyda72uq7f463ne77e4tn99steprmz7" + pre, _ := staking.NewPrecompile(nil, nil, nil, nil) + delegationMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).DelegationID) + shares := 100 + delegationResponse := &stakingtypes.QueryDelegationResponse{ + DelegationResponse: &stakingtypes.DelegationResponse{ + Delegation: stakingtypes.Delegation{ + DelegatorAddress: callerSeiAddress.String(), + ValidatorAddress: validatorAddress, + Shares: sdk.NewDec(int64(shares)), + }, + Balance: sdk.NewCoin("usei", sdk.NewInt(int64(shares))), + }, + } + hundredSharesValue := new(big.Int) + hundredSharesValue.SetString("100000000000000000000", 10) + delegation := staking.Delegation{ + Balance: staking.Balance{ + Amount: big.NewInt(int64(shares)), + Denom: "usei", + }, + Delegation: staking.DelegationDetails{ + DelegatorAddress: callerSeiAddress.String(), + Shares: hundredSharesValue, + Decimals: big.NewInt(sdk.Precision), + ValidatorAddress: validatorAddress, + }, + } + + happyPathPackedOutput, _ := delegationMethod.Outputs.Pack(delegation) + + type fields struct { + Precompile pcommon.Precompile + stakingKeeper pcommon.StakingKeeper + stakingQuerier pcommon.StakingQuerier + evmKeeper pcommon.EVMKeeper + } + type args struct { + evm *vm.EVM + delegatorAddress common.Address + validatorAddress string + caller common.Address + callingContract common.Address + value *big.Int + readOnly bool + } + + tests := []struct { + name string + fields fields + args args + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + delegatorAddress: callerEvmAddress, + validatorAddress: validatorAddress, + value: big.NewInt(100), + }, + wantRet: happyPathPackedOutput, + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "fails if caller != callingContract", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + caller: callerEvmAddress, + callingContract: contractEvmAddress, + delegatorAddress: callerEvmAddress, + validatorAddress: validatorAddress, + value: big.NewInt(100), + }, + wantErr: true, + wantErrMsg: "cannot delegatecall staking", + }, + { + name: "fails if delegator address unassociated", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + caller: callerEvmAddress, + callingContract: callerEvmAddress, + delegatorAddress: unassociatedEvmAddress, + validatorAddress: validatorAddress, + }, + wantErr: true, + wantErrMsg: fmt.Sprintf("address %s is not linked", unassociatedEvmAddress.String()), + }, + { + name: "fails if delegator address is invalid", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + delegatorAddress: common.Address{}, + validatorAddress: validatorAddress, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + }, + wantErr: true, + wantErrMsg: "invalid addr", + }, + { + name: "should return error if delegation not found", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Err: fmt.Errorf("delegation with delegator %s not found for validator", callerSeiAddress.String()), + }, + }, + args: args{ + delegatorAddress: callerEvmAddress, + validatorAddress: validatorAddress, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + }, + wantErr: true, + wantErrMsg: fmt.Sprintf("delegation with delegator %s not found for validator", callerSeiAddress.String()), + }, + { + name: "should return delegation details", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + delegatorAddress: callerEvmAddress, + validatorAddress: validatorAddress, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + }, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + { + name: "should allow static call", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + delegatorAddress: callerEvmAddress, + validatorAddress: validatorAddress, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + readOnly: true, + }, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + k.SetAddressMapping(ctx, callerSeiAddress, callerEvmAddress) + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + TxContext: vm.TxContext{Origin: callerEvmAddress}, + } + p, _ := staking.NewPrecompile(tt.fields.stakingKeeper, tt.fields.stakingQuerier, k, nil) + delegation, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).DelegationID) + require.Nil(t, err) + inputs, err := delegation.Inputs.Pack(tt.args.delegatorAddress, tt.args.validatorAddress) + require.Nil(t, err) + gotRet, err := p.Run(&evm, tt.args.caller, tt.args.callingContract, append(p.GetExecutor().(*staking.PrecompileExecutor).DelegationID, inputs...), tt.args.value, tt.args.readOnly) + if (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Equal(t, tt.wantErrMsg, string(gotRet)) + } else if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index 865e94011..86b923b53 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -1,7 +1,6 @@ package wasmd import ( - "bytes" "embed" "encoding/json" "errors" @@ -53,15 +52,7 @@ type ExecuteMsg struct { } func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.DynamicGasPrecompile, error) { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - return nil, fmt.Errorf("error loading the wasmd ABI %s", err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - return nil, err - } + newAbi := pcommon.MustGetABI(f, "abi.json") executor := &PrecompileExecutor{ wasmdKeeper: wasmdKeeper, diff --git a/precompiles/wasmd/wasmd_test.go b/precompiles/wasmd/wasmd_test.go index 8a7743f60..b653ed14d 100644 --- a/precompiles/wasmd/wasmd_test.go +++ b/precompiles/wasmd/wasmd_test.go @@ -68,7 +68,7 @@ func TestInstantiate(t *testing.T) { require.Equal(t, 2, len(outputs)) require.Equal(t, "sei1hrpna9v7vs3stzyd4z3xf00676kf78zpe2u5ksvljswn2vnjp3yslucc3n", outputs[0].(string)) require.Empty(t, outputs[1].([]byte)) - require.Equal(t, uint64(881127), g) + require.NotZero(t, g) amtsbz, err = sdk.NewCoins().MarshalJSON() require.Nil(t, err) @@ -91,7 +91,7 @@ func TestInstantiate(t *testing.T) { require.Equal(t, 2, len(outputs)) require.Equal(t, "sei1hrpna9v7vs3stzyd4z3xf00676kf78zpe2u5ksvljswn2vnjp3yslucc3n", outputs[0].(string)) require.Empty(t, outputs[1].([]byte)) - require.Equal(t, uint64(904183), g) + require.NotZero(t, g) // non-existent code ID args, _ = instantiateMethod.Inputs.Pack( @@ -154,8 +154,9 @@ func TestExecute(t *testing.T) { testApp.BankKeeper.SendCoins(ctx, mockAddr, testApp.EvmKeeper.GetSeiAddressOrDefault(ctx, common.HexToAddress(wasmd.WasmdAddress)), amts) // circular interop statedb.WithCtx(statedb.Ctx().WithIsEVM(false)) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.GetExecutor().(*wasmd.PrecompileExecutor).ExecuteID, args...), suppliedGas, big.NewInt(1000_000_000_000_000), nil, false) - require.Equal(t, "sei does not support CW->EVM->CW call pattern", err.Error()) + res, _, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.GetExecutor().(*wasmd.PrecompileExecutor).ExecuteID, args...), suppliedGas, big.NewInt(1000_000_000_000_000), nil, false) + require.Equal(t, "sei does not support CW->EVM->CW call pattern", string(res)) + require.Equal(t, vm.ErrExecutionReverted, err) statedb.WithCtx(statedb.Ctx().WithIsEVM(true)) res, g, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.GetExecutor().(*wasmd.PrecompileExecutor).ExecuteID, args...), suppliedGas, big.NewInt(1000_000_000_000_000), nil, false) require.Nil(t, err) @@ -163,7 +164,7 @@ func TestExecute(t *testing.T) { require.Nil(t, err) require.Equal(t, 1, len(outputs)) require.Equal(t, fmt.Sprintf("received test msg from %s with 1000usei", mockAddr.String()), string(outputs[0].([]byte))) - require.Equal(t, uint64(907386), g) + require.NotZero(t, g) require.Equal(t, int64(1000), testApp.BankKeeper.GetBalance(statedb.Ctx(), contractAddr, "usei").Amount.Int64()) amtsbz, err = sdk.NewCoins().MarshalJSON() @@ -252,7 +253,7 @@ func TestQuery(t *testing.T) { require.Nil(t, err) require.Equal(t, 1, len(outputs)) require.Equal(t, "{\"message\":\"query test\"}", string(outputs[0].([]byte))) - require.Equal(t, uint64(931712), g) + require.NotZero(t, g) // bad contract address args, _ = queryMethod.Inputs.Pack(mockAddr.String(), []byte("{\"info\":{}}")) @@ -315,7 +316,7 @@ func TestExecuteBatchOneMessage(t *testing.T) { require.Nil(t, err) require.Equal(t, 1, len(outputs)) require.Equal(t, fmt.Sprintf("received test msg from %s with 1000usei", mockAddr.String()), string((outputs[0].([][]byte))[0])) - require.Equal(t, uint64(907386), g) + require.NotZero(t, g) require.Equal(t, int64(1000), testApp.BankKeeper.GetBalance(statedb.Ctx(), contractAddr, "usei").Amount.Int64()) amtsbz, err = sdk.NewCoins().MarshalJSON() @@ -466,7 +467,7 @@ func TestExecuteBatchMultipleMessages(t *testing.T) { require.Equal(t, fmt.Sprintf("received test msg from %s with 1000usei", mockAddr.String()), string(parsedOutputs[0])) require.Equal(t, fmt.Sprintf("received test msg from %s with 1000usei", mockAddr.String()), string(parsedOutputs[1])) require.Equal(t, fmt.Sprintf("received test msg from %s with 1000usei", mockAddr.String()), string(parsedOutputs[2])) - require.Equal(t, uint64(726724), g) + require.NotZero(t, g) require.Equal(t, int64(3000), testApp.BankKeeper.GetBalance(statedb.Ctx(), contractAddr, "usei").Amount.Int64()) amtsbz2, err := sdk.NewCoins().MarshalJSON() @@ -493,7 +494,7 @@ func TestExecuteBatchMultipleMessages(t *testing.T) { require.Equal(t, fmt.Sprintf("received test msg from %s with", mockAddr.String()), string(parsedOutputs[0])) require.Equal(t, fmt.Sprintf("received test msg from %s with 1000usei", mockAddr.String()), string(parsedOutputs[1])) require.Equal(t, fmt.Sprintf("received test msg from %s with", mockAddr.String()), string(parsedOutputs[2])) - require.Equal(t, uint64(775245), g) + require.NotZero(t, g) require.Equal(t, int64(1000), testApp.BankKeeper.GetBalance(statedb.Ctx(), contractAddr, "usei").Amount.Int64()) // allowed delegatecall diff --git a/proto/evm/receipt.proto b/proto/evm/receipt.proto index 49751bb01..4f89a62fb 100644 --- a/proto/evm/receipt.proto +++ b/proto/evm/receipt.proto @@ -10,6 +10,7 @@ message Log { repeated string topics = 2; bytes data = 3; uint32 index = 4; + bool synthetic = 5; } message Receipt { diff --git a/readme.md b/readme.md index 239bdd79e..29dd6b34b 100644 --- a/readme.md +++ b/readme.md @@ -2,31 +2,30 @@ ![Banner!](assets/SeiLogo.png) -Sei is the fastest general purpose L1 blockchain offering the best infrastructure for the exchange of digital assets. The chain emphasizes reliability, security and high throughput above all else, enabling an entirely new echelon of ultra-high performance DeFi products built on top. Sei's on-chain CLOB and matching engine provides deep liquidity and price-time-priority matching for traders and apps. Apps built on Sei benefit from built-in orderbook infrastructure, deep liquidity, and a fully decentralized matching service. Users benefit from this exchange model with the ability to select price, size, and direction of their trades coupled with MEV protection. +Sei is the fastest general purpose L1 blockchain and the first parallelized EVM. This allows Sei to get the best of Solana and Ethereum - a hyper optimized execution layer that benefits from the tooling and mindshare around the EVM. -# Sei -**Sei** is a blockchain built using Cosmos SDK and Tendermint. It is built using the Cosmos SDK and Tendermint core, and features a built-in central limit orderbook (CLOB) module. Decentralized applications building on Sei can build on top of the CLOB, and other Cosmos-based blockchains can leverage Sei's CLOB as a shared liquidity hub and create markets for any asset. - -Designed with developers and users in mind, Sei serves as the infrastructure and shared liquidity hub for the next generation of DeFi. Apps can easily plug-and-play to trade on Sei orderbook infrastructure and access pooled liquidity from other apps. To prioritize developer experience, Sei Network has integrated the wasmd module to support CosmWasm smart contracts. +# Overview +**Sei** is a high-performance, low-fee, delegated proof-of-stake blockchain designed for developers. It supports optimistic parallel execution of both EVM and CosmWasm, opening up new design possibilities. With unique optimizations like twin turbo consensus and SeiDB, Sei ensures consistent 400ms block times and a transaction throughput that’s orders of magnitude higher than Ethereum. This means faster, more cost-effective operations. Plus, Sei’s seamless interoperability between EVM and CosmWasm gives developers native access to the entire Cosmos ecosystem, including IBC tokens, multi-sig accounts, fee grants, and more. # Documentation -For the most up to date documentation please visit https://www.sei.io/ +For the most up to date documentation please visit https://www.docs.sei.io/ -# Sei Ecosystem -Sei Network is an L1 blockchain with a built-in on-chain orderbook that allows smart contracts easy access to shared liquidity. Sei architecture enables composable apps that maintain modularity. +# Sei Optimizations +Sei introduces four major innovations: -Sei Network serves as the matching core of the ecosystem, offering superior reliability and ultra-high transaction speed to ecosystem partners, each with their own functionality and user experience. Anyone can create a DeFi application that leverages Sei's liquidity and the entire ecosystem benefits. +- Twin Turbo Consensus: This feature allows Sei to reach the fastest time to finality of any blockchain at 400ms, unlocking web2 like experiences for applications. +- Optimistic Parallelization: This feature allows developers to unlock parallel processing for their Ethereum applications, with no additional work. +- SeiDB: This major upgrade allows Sei to handle the much higher rate of data storage, reads and writes which become extremely important for a high performance blockchain. +- Interoperable EVM: This allows existing developers in the Ethereum ecosystem to deploy their applications, tooling and infrastructure to Sei with no changes, while benefiting from the 100x performance improvements offered by Sei. -Developers, traders, and users can all connect to Sei as ecosystem partners benefiting from shared liquidity and decentralized financial primitives. +All these features combine to unlock a brand new, scalable design space for the Ethereum Ecosystem. # Testnet ## Get started **How to validate on the Sei Testnet** -*This is the Sei Testnet-1 (sei-testnet-1)* - -> Genesis [Published](https://github.com/sei-protocol/testnet/blob/main/sei-testnet-1/genesis.json) +*This is the Sei Atlantic-2 Testnet ()* -> Peers [Published](https://github.com/sei-protocol/testnet/blob/main/sei-testnet-1/addrbook.json) +> Genesis [Published](https://github.com/sei-protocol/testnet/blob/main/atlantic-2/genesis.json) ## Hardware Requirements **Minimum** diff --git a/scripts/initialize_local_chain.sh b/scripts/initialize_local_chain.sh index 68773c0a9..ff17168f1 100755 --- a/scripts/initialize_local_chain.sh +++ b/scripts/initialize_local_chain.sh @@ -60,7 +60,7 @@ cat ~/.sei/config/genesis.json | jq '.app_state["oracle"]["params"]["whitelist"] cat ~/.sei/config/genesis.json | jq '.app_state["distribution"]["params"]["community_tax"]="0.000000000000000000"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json cat ~/.sei/config/genesis.json | jq '.consensus_params["block"]["max_gas"]="35000000"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json cat ~/.sei/config/genesis.json | jq '.app_state["staking"]["params"]["max_voting_power_ratio"]="1.000000000000000000"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json -cat ~/.sei/config/genesis.json | jq '.app_state["bank"]["denom_metadata"]=[{"denom_units":[{"denom":"UATOM","exponent":6,"aliases":["UATOM"]}],"base":"uatom","display":"uatom","name":"UATOM","symbol":"UATOM"}]' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json +cat ~/.sei/config/genesis.json | jq '.app_state["bank"]["denom_metadata"]=[{"denom_units":[{"denom":"usei","exponent":0,"aliases":["USEI"]}],"base":"usei","display":"usei","name":"USEI","symbol":"USEI"}]' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json # Use the Python command to get the dates START_DATE=$($PYTHON_CMD -c "from datetime import datetime; print(datetime.now().strftime('%Y-%m-%d'))") diff --git a/testutil/keeper/evm.go b/testutil/keeper/evm.go index d5449ad59..bd2df8b44 100644 --- a/testutil/keeper/evm.go +++ b/testutil/keeper/evm.go @@ -100,3 +100,7 @@ func PrivateKeyToAddresses(privKey cryptotypes.PrivKey) (sdk.AccAddress, common. return sdk.AccAddress(privKey.PubKey().Address()), crypto.PubkeyToAddress(*pubKey) } + +func UseiCoins(amount int64) sdk.Coins { + return sdk.NewCoins(sdk.NewCoin(sdk.MustGetBaseDenom(), sdk.NewInt(amount))) +} diff --git a/utils/helpers/address.go b/utils/helpers/address.go new file mode 100644 index 000000000..e632a893b --- /dev/null +++ b/utils/helpers/address.go @@ -0,0 +1,65 @@ +package helpers + +import ( + "errors" + "math/big" + + "github.com/btcsuite/btcd/btcec" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/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/crypto" +) + +func GetAddresses(V *big.Int, R *big.Int, S *big.Int, data common.Hash) (common.Address, sdk.AccAddress, cryptotypes.PubKey, error) { + pubkey, err := RecoverPubkey(data, R, S, V, true) + if err != nil { + return common.Address{}, sdk.AccAddress{}, nil, err + } + + evmAddr, err := PubkeyToEVMAddress(pubkey) + if err != nil { + return common.Address{}, sdk.AccAddress{}, nil, err + } + seiPubkey := PubkeyBytesToSeiPubKey(pubkey) + seiAddr := sdk.AccAddress(seiPubkey.Address()) + return evmAddr, seiAddr, &seiPubkey, nil +} + +// first half of go-ethereum/core/types/transaction_signing.go:recoverPlain +func RecoverPubkey(sighash common.Hash, R, S, Vb *big.Int, homestead bool) ([]byte, error) { + if Vb.BitLen() > 8 { + return []byte{}, ethtypes.ErrInvalidSig + } + V := byte(Vb.Uint64() - 27) + if !crypto.ValidateSignatureValues(V, R, S, homestead) { + return []byte{}, ethtypes.ErrInvalidSig + } + // encode the signature in uncompressed format + r, s := R.Bytes(), S.Bytes() + sig := make([]byte, crypto.SignatureLength) + copy(sig[32-len(r):32], r) + copy(sig[64-len(s):64], s) + sig[64] = V + + // recover the public key from the signature + return crypto.Ecrecover(sighash[:], sig) +} + +// second half of go-ethereum/core/types/transaction_signing.go:recoverPlain +func PubkeyToEVMAddress(pub []byte) (common.Address, error) { + if len(pub) == 0 || pub[0] != 4 { + return common.Address{}, errors.New("invalid public key") + } + var addr common.Address + copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + return addr, nil +} + +func PubkeyBytesToSeiPubKey(pub []byte) secp256k1.PubKey { + pubKey, _ := crypto.UnmarshalPubkey(pub) + pubkeyObj := (*btcec.PublicKey)(pubKey) + return secp256k1.PubKey{Key: pubkeyObj.SerializeCompressed()} +} diff --git a/utils/helpers/associate.go b/utils/helpers/associate.go new file mode 100644 index 000000000..58fffe274 --- /dev/null +++ b/utils/helpers/associate.go @@ -0,0 +1,50 @@ +package helpers + +import ( + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/common" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" +) + +type AssociationHelper struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + accountKeeper pcommon.AccountKeeper +} + +func NewAssociationHelper(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, accountKeeper pcommon.AccountKeeper) *AssociationHelper { + return &AssociationHelper{evmKeeper: evmKeeper, bankKeeper: bankKeeper, accountKeeper: accountKeeper} +} + +func (p AssociationHelper) AssociateAddresses(ctx sdk.Context, seiAddr sdk.AccAddress, evmAddr common.Address, pubkey cryptotypes.PubKey) error { + p.evmKeeper.SetAddressMapping(ctx, seiAddr, evmAddr) + if acc := p.accountKeeper.GetAccount(ctx, seiAddr); acc.GetPubKey() == nil { + if err := acc.SetPubKey(pubkey); err != nil { + return err + } + p.accountKeeper.SetAccount(ctx, acc) + } + return p.MigrateBalance(ctx, evmAddr, seiAddr) +} + +func (p AssociationHelper) MigrateBalance(ctx sdk.Context, evmAddr common.Address, seiAddr sdk.AccAddress) error { + castAddr := sdk.AccAddress(evmAddr[:]) + castAddrBalances := p.bankKeeper.SpendableCoins(ctx, castAddr) + if !castAddrBalances.IsZero() { + if err := p.bankKeeper.SendCoins(ctx, castAddr, seiAddr, castAddrBalances); err != nil { + return err + } + } + castAddrWei := p.bankKeeper.GetWeiBalance(ctx, castAddr) + if !castAddrWei.IsZero() { + if err := p.bankKeeper.SendCoinsAndWei(ctx, castAddr, seiAddr, sdk.ZeroInt(), castAddrWei); err != nil { + return err + } + } + if p.bankKeeper.LockedCoins(ctx, castAddr).IsZero() { + p.accountKeeper.RemoveAccount(ctx, authtypes.NewBaseAccountWithAddress(castAddr)) + } + return nil +} diff --git a/utils/metadata.go b/utils/metadata.go new file mode 100644 index 000000000..04b20d7cb --- /dev/null +++ b/utils/metadata.go @@ -0,0 +1,7 @@ +package utils + +type ERCMetadata struct { + Name string + Symbol string + Decimals uint8 +} diff --git a/x/dex/module.go b/x/dex/module.go index 966699406..0a82142be 100644 --- a/x/dex/module.go +++ b/x/dex/module.go @@ -86,6 +86,27 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingCo return genState.Validate() } +// ExportGenesisStream returns the tokenfactory module's exported genesis state as raw JSON bytes in a streaming fashion. +func (am AppModule) ExportGenesisStream(ctx sdk.Context, cdc codec.JSONCodec) <-chan json.RawMessage { + ch := make(chan json.RawMessage) + go func() { + ch <- am.ExportGenesis(ctx, cdc) + close(ch) + }() + return ch +} + +// ValidateGenesisStream performs genesis state validation for the x/tokenfactory module in a streaming fashion. +func (am AppModuleBasic) ValidateGenesisStream(cdc codec.JSONCodec, config client.TxEncodingConfig, genesisCh <-chan json.RawMessage) error { + for genesis := range genesisCh { + err := am.ValidateGenesis(cdc, config, genesis) + if err != nil { + return err + } + } + return nil +} + // RegisterRESTRoutes registers the capability module's REST service handlers. func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) { } diff --git a/x/dex/utils/context.go b/x/dex/utils/context.go index 1b23c04f5..7c7ca2740 100644 --- a/x/dex/utils/context.go +++ b/x/dex/utils/context.go @@ -14,5 +14,5 @@ func GetMemState(ctx context.Context) *dexcache.MemState { if val := ctx.Value(DexMemStateContextKey); val != nil { return val.(*dexcache.MemState) } - panic("cannot find mem state in context") + return nil } diff --git a/x/epoch/module.go b/x/epoch/module.go index b030dfce5..f978e5919 100644 --- a/x/epoch/module.go +++ b/x/epoch/module.go @@ -74,6 +74,17 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingCo return genState.Validate() } +// ValidateGenesisStream performs genesis state validation for the capability module in a streaming fashion. +func (am AppModuleBasic) ValidateGenesisStream(cdc codec.JSONCodec, config client.TxEncodingConfig, genesisCh <-chan json.RawMessage) error { + for genesis := range genesisCh { + err := am.ValidateGenesis(cdc, config, genesis) + if err != nil { + return err + } + } + return nil +} + // RegisterRESTRoutes registers the capability module's REST service handlers. func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {} @@ -164,6 +175,16 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw return cdc.MustMarshalJSON(genState) } +// ExportGenesisStream returns the capability module's exported genesis state as raw JSON bytes in a streaming fashion. +func (am AppModule) ExportGenesisStream(ctx sdk.Context, cdc codec.JSONCodec) <-chan json.RawMessage { + ch := make(chan json.RawMessage) + go func() { + ch <- am.ExportGenesis(ctx, cdc) + close(ch) + }() + return ch +} + // ConsensusVersion implements ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 2 } diff --git a/x/evm/ante/preprocess.go b/x/evm/ante/preprocess.go index 2a1191927..a1f49efd8 100644 --- a/x/evm/ante/preprocess.go +++ b/x/evm/ante/preprocess.go @@ -2,13 +2,13 @@ package ante import ( "encoding/hex" - "errors" "fmt" "math/big" + "github.com/sei-protocol/sei-chain/utils/helpers" + "github.com/btcsuite/btcd/btcec" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -71,6 +71,7 @@ func (p *EVMPreprocessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate sdk.NewAttribute(evmtypes.AttributeKeySeiAddress, seiAddr.String()))) pubkey := derived.PubKey isAssociateTx := derived.IsAssociate + associateHelper := helpers.NewAssociationHelper(p.evmKeeper, p.evmKeeper.BankKeeper(), p.accountKeeper) _, isAssociated := p.evmKeeper.GetEVMAddress(ctx, seiAddr) if isAssociateTx && isAssociated { return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "account already has association set") @@ -80,15 +81,16 @@ func (p *EVMPreprocessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate metrics.IncrementAssociationError("associate_tx_insufficient_funds", evmtypes.NewAssociationMissingErr(seiAddr.String())) return ctx, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "account needs to have at least 1 wei to force association") } - if err := p.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey); err != nil { + if err := associateHelper.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey); err != nil { return ctx, err } + return ctx.WithPriority(antedecorators.EVMAssociatePriority), nil // short-circuit without calling next } else if isAssociated { // noop; for readability } else { // not associatedTx and not already associated - if err := p.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey); err != nil { + if err := associateHelper.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey); err != nil { return ctx, err } if p.evmKeeper.EthReplayConfig.Enabled { @@ -99,17 +101,6 @@ func (p *EVMPreprocessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate return next(ctx, tx, simulate) } -func (p *EVMPreprocessDecorator) AssociateAddresses(ctx sdk.Context, seiAddr sdk.AccAddress, evmAddr common.Address, pubkey cryptotypes.PubKey) error { - p.evmKeeper.SetAddressMapping(ctx, seiAddr, evmAddr) - if acc := p.accountKeeper.GetAccount(ctx, seiAddr); acc.GetPubKey() == nil { - if err := acc.SetPubKey(pubkey); err != nil { - return err - } - p.accountKeeper.SetAccount(ctx, acc) - } - return MigrateBalance(ctx, p.evmKeeper, evmAddr, seiAddr) -} - func (p *EVMPreprocessDecorator) IsAccountBalancePositive(ctx sdk.Context, seiAddr sdk.AccAddress, evmAddr common.Address) bool { baseDenom := p.evmKeeper.GetBaseDenom(ctx) if amt := p.evmKeeper.BankKeeper().GetBalance(ctx, seiAddr, baseDenom).Amount; amt.IsPositive() { @@ -144,7 +135,7 @@ func Preprocess(ctx sdk.Context, msgEVMTransaction *evmtypes.MsgEVMTransaction) V = new(big.Int).Add(V, utils.Big27) // Hash custom message passed in customMessageHash := crypto.Keccak256Hash([]byte(atx.CustomMessage)) - evmAddr, seiAddr, pubkey, err := getAddresses(V, R, S, customMessageHash) + evmAddr, seiAddr, pubkey, err := helpers.GetAddresses(V, R, S, customMessageHash) if err != nil { return err } @@ -176,7 +167,7 @@ func Preprocess(ctx sdk.Context, msgEVMTransaction *evmtypes.MsgEVMTransaction) } else { txHash = ethtypes.FrontierSigner{}.Hash(ethTx) } - evmAddr, seiAddr, seiPubkey, err := getAddresses(V, R, S, txHash) + evmAddr, seiAddr, seiPubkey, err := helpers.GetAddresses(V, R, S, txHash) if err != nil { return err } @@ -243,55 +234,6 @@ func (p *EVMPreprocessDecorator) AnteDeps(txDeps []sdkacltypes.AccessOperation, }), tx, txIndex) } -func getAddresses(V *big.Int, R *big.Int, S *big.Int, data common.Hash) (common.Address, sdk.AccAddress, cryptotypes.PubKey, error) { - pubkey, err := recoverPubkey(data, R, S, V, true) - if err != nil { - return common.Address{}, sdk.AccAddress{}, nil, err - } - evmAddr, err := pubkeyToEVMAddress(pubkey) - if err != nil { - return common.Address{}, sdk.AccAddress{}, nil, err - } - seiPubkey := pubkeyBytesToSeiPubKey(pubkey) - seiAddr := sdk.AccAddress(seiPubkey.Address()) - return evmAddr, seiAddr, &seiPubkey, nil -} - -// first half of go-ethereum/core/types/transaction_signing.go:recoverPlain -func recoverPubkey(sighash common.Hash, R, S, Vb *big.Int, homestead bool) ([]byte, error) { - if Vb.BitLen() > 8 { - return []byte{}, ethtypes.ErrInvalidSig - } - V := byte(Vb.Uint64() - 27) - if !crypto.ValidateSignatureValues(V, R, S, homestead) { - return []byte{}, ethtypes.ErrInvalidSig - } - // encode the signature in uncompressed format - r, s := R.Bytes(), S.Bytes() - sig := make([]byte, crypto.SignatureLength) - copy(sig[32-len(r):32], r) - copy(sig[64-len(s):64], s) - sig[64] = V - // recover the public key from the signature - return crypto.Ecrecover(sighash[:], sig) -} - -// second half of go-ethereum/core/types/transaction_signing.go:recoverPlain -func pubkeyToEVMAddress(pub []byte) (common.Address, error) { - if len(pub) == 0 || pub[0] != 4 { - return common.Address{}, errors.New("invalid public key") - } - var addr common.Address - copy(addr[:], crypto.Keccak256(pub[1:])[12:]) - return addr, nil -} - -func pubkeyBytesToSeiPubKey(pub []byte) secp256k1.PubKey { - pubKey, _ := crypto.UnmarshalPubkey(pub) - pubkeyObj := (*btcec.PublicKey)(pubKey) - return secp256k1.PubKey{Key: pubkeyObj.SerializeCompressed()} -} - func isTxTypeAllowed(version derived.SignerVersion, txType uint8) bool { for _, t := range AllowedTxTypes[version] { if t == txType { @@ -332,26 +274,6 @@ func NewEVMAddressDecorator(evmKeeper *evmkeeper.Keeper, accountKeeper *accountk return &EVMAddressDecorator{evmKeeper: evmKeeper, accountKeeper: accountKeeper} } -func MigrateBalance(ctx sdk.Context, evmKeeper *evmkeeper.Keeper, evmAddr common.Address, seiAddr sdk.AccAddress) error { - castAddr := sdk.AccAddress(evmAddr[:]) - castAddrBalances := evmKeeper.BankKeeper().SpendableCoins(ctx, castAddr) - if !castAddrBalances.IsZero() { - if err := evmKeeper.BankKeeper().SendCoins(ctx, castAddr, seiAddr, castAddrBalances); err != nil { - return err - } - } - castAddrWei := evmKeeper.BankKeeper().GetWeiBalance(ctx, castAddr) - if !castAddrWei.IsZero() { - if err := evmKeeper.BankKeeper().SendCoinsAndWei(ctx, castAddr, seiAddr, sdk.ZeroInt(), castAddrWei); err != nil { - return err - } - } - if evmKeeper.BankKeeper().LockedCoins(ctx, castAddr).IsZero() { - evmKeeper.AccountKeeper().RemoveAccount(ctx, authtypes.NewBaseAccountWithAddress(castAddr)) - } - return nil -} - //nolint:revive func (p *EVMAddressDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { sigTx, ok := tx.(authsigning.SigVerifiableTx) @@ -380,7 +302,7 @@ func (p *EVMAddressDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo sdk.NewAttribute(evmtypes.AttributeKeySeiAddress, signer.String()))) continue } - evmAddr, err := pubkeyToEVMAddress(pk.SerializeUncompressed()) + evmAddr, err := helpers.PubkeyToEVMAddress(pk.SerializeUncompressed()) if err != nil { ctx.Logger().Error(fmt.Sprintf("failed to get EVM address from pubkey due to %s", err)) ctx.EventManager().EmitEvent(sdk.NewEvent(evmtypes.EventTypeSigner, @@ -391,7 +313,8 @@ func (p *EVMAddressDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo sdk.NewAttribute(evmtypes.AttributeKeyEvmAddress, evmAddr.Hex()), sdk.NewAttribute(evmtypes.AttributeKeySeiAddress, signer.String()))) p.evmKeeper.SetAddressMapping(ctx, signer, evmAddr) - if err := MigrateBalance(ctx, p.evmKeeper, evmAddr, signer); err != nil { + associationHelper := helpers.NewAssociationHelper(p.evmKeeper, p.evmKeeper.BankKeeper(), p.accountKeeper) + if err := associationHelper.MigrateBalance(ctx, evmAddr, signer); err != nil { ctx.Logger().Error(fmt.Sprintf("failed to migrate EVM address balance (%s) %s", evmAddr.Hex(), err)) return ctx, err } diff --git a/x/evm/ante/preprocess_test.go b/x/evm/ante/preprocess_test.go index e7c016d78..0c20790f4 100644 --- a/x/evm/ante/preprocess_test.go +++ b/x/evm/ante/preprocess_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "github.com/sei-protocol/sei-chain/utils/helpers" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" @@ -302,7 +304,8 @@ func TestMigrateBalance(t *testing.T) { k.AccountKeeper().NewAccountWithAddress(ctx, sdk.AccAddress(evmAddr[:])).(*authtypes.BaseAccount), sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1))), math.MaxInt64, admin), )) - require.Nil(t, ante.MigrateBalance(ctx, k, evmAddr, seiAddr)) + associateHelper := helpers.NewAssociationHelper(k, k.BankKeeper(), k.AccountKeeper()) + require.Nil(t, associateHelper.MigrateBalance(ctx, evmAddr, seiAddr)) require.Equal(t, int64(1), k.BankKeeper().SpendableCoins(ctx, seiAddr).AmountOf("usei").Int64()) require.Equal(t, int64(0), k.BankKeeper().LockedCoins(ctx, seiAddr).AmountOf("usei").Int64()) require.Equal(t, int64(0), k.BankKeeper().SpendableCoins(ctx, sdk.AccAddress(evmAddr[:])).AmountOf("usei").Int64()) diff --git a/x/evm/artifacts/artifacts.go b/x/evm/artifacts/artifacts.go new file mode 100644 index 000000000..9c8a497c5 --- /dev/null +++ b/x/evm/artifacts/artifacts.go @@ -0,0 +1,36 @@ +package artifacts + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw721" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" +) + +func GetParsedABI(typ string) *abi.ABI { + switch typ { + case "native": + return native.GetParsedABI() + case "cw20": + return cw20.GetParsedABI() + case "cw721": + return cw721.GetParsedABI() + default: + panic(fmt.Sprintf("unknown artifact type %s", typ)) + } +} + +func GetBin(typ string) []byte { + switch typ { + case "native": + return native.GetBin() + case "cw20": + return cw20.GetBin() + case "cw721": + return cw721.GetBin() + default: + panic(fmt.Sprintf("unknown artifact type %s", typ)) + } +} diff --git a/x/evm/artifacts/cw20/CW20ERC20Pointer.bin b/x/evm/artifacts/cw20/CW20ERC20Pointer.bin index 8961633ac..c27324793 100644 --- a/x/evm/artifacts/cw20/CW20ERC20Pointer.bin +++ b/x/evm/artifacts/cw20/CW20ERC20Pointer.bin @@ -1 +1 @@ -608060405234801561001057600080fd5b50604051611bf9380380611bf983398101604081905261002f91610150565b8181600361003d8382610262565b50600461004a8282610262565b5050600680546001600160a01b031990811661100217909155600780548216611003179055600880549091166110041790555060056100898482610262565b50505050610321565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126100b957600080fd5b81516001600160401b03808211156100d3576100d3610092565b604051601f8301601f19908116603f011681019082821181831017156100fb576100fb610092565b816040528381526020925086602085880101111561011857600080fd5b600091505b8382101561013a578582018301518183018401529082019061011d565b6000602085830101528094505050505092915050565b60008060006060848603121561016557600080fd5b83516001600160401b038082111561017c57600080fd5b610188878388016100a8565b9450602086015191508082111561019e57600080fd5b6101aa878388016100a8565b935060408601519150808211156101c057600080fd5b506101cd868287016100a8565b9150509250925092565b600181811c908216806101eb57607f821691505b60208210810361020b57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561025d576000816000526020600020601f850160051c8101602086101561023a5750805b601f850160051c820191505b8181101561025957828155600101610246565b5050505b505050565b81516001600160401b0381111561027b5761027b610092565b61028f8161028984546101d7565b84610211565b602080601f8311600181146102c457600084156102ac5750858301515b600019600386901b1c1916600185901b178555610259565b600085815260208120601f198616915b828110156102f3578886015182559484019460019091019084016102d4565b50858210156103115787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6118c9806103306000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806395d89b411161008c578063da73d16b11610066578063da73d16b146101b1578063dd62ed3e146101b9578063de4725cc146101cc578063f00b0255146101df57600080fd5b806395d89b411461016b578063a9059cbb14610173578063c2aed3021461018657600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012b578063313ce5671461013e57806370a0823114610158575b600080fd5b6100dc6101f2565b6040516100e9919061133f565b60405180910390f35b61010561010036600461136e565b610284565b60405190151581526020016100e9565b61011d61055f565b6040519081526020016100e9565b610105610139366004611398565b6106a2565b61014661089b565b60405160ff90911681526020016100e9565b61011d6101663660046113d4565b610996565b6100dc610b86565b61010561018136600461136e565b610b95565b600854610199906001600160a01b031681565b6040516001600160a01b0390911681526020016100e9565b6100dc610cf5565b61011d6101c73660046113ef565b610d83565b600754610199906001600160a01b031681565b600654610199906001600160a01b031681565b60606003805461020190611422565b80601f016020809104026020016040519081016040528092919081815260200182805461022d90611422565b801561027a5780601f1061024f5761010080835404028352916020019161027a565b820191906000526020600020905b81548152906001019060200180831161025d57829003601f168201915b5050505050905090565b60006001600160801b038211156102a0576001600160801b0391505b60006102ac3385610d83565b90508281111561040d57604080518082018252600781526639b832b73232b960c91b60208201526008549151630c3c20ed60e01b81526001600160a01b03878116600483015260009361035b93926103569290911690630c3c20ed906024015b600060405180830381865afa158015610329573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261035191908101906114e6565b610f85565b610fcb565b9050600061039860405180604001604052806006815260200165185b5bdd5b9d60d21b81525061035661035188876103939190611537565b611000565b905060006103f96103f46040518060400160405280601281526020017164656372656173655f616c6c6f77616e636560701b8152506103566103f48787604051806040016040528060018152602001600b60fa1b815250611093565b6110df565b90506104048161110f565b50505050610513565b8281101561051357604080518082018252600781526639b832b73232b960c91b60208201526008549151630c3c20ed60e01b81526001600160a01b03878116600483015260009361046f93926103569290911690630c3c20ed9060240161030c565b905060006104a760405180604001604052806006815260200165185b5bdd5b9d60d21b81525061035661035186896103939190611537565b905060006105036103f460405180604001604052806012815260200171696e6372656173655f616c6c6f77616e636560701b8152506103566103f48787604051806040016040528060018152602001600b60fa1b815250611093565b905061050e8161110f565b505050505b6040518381526001600160a01b0385169033907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259060200160405180910390a360019150505b92915050565b6000806105ab6103f46040518060400160405280600a815260200169746f6b656e5f696e666f60b01b815250604051806040016040528060028152602001617b7d60f01b815250610fcb565b6006546040516306d81d2960e01b81529192506000916001600160a01b03909116906306d81d29906105e49060059086906004016115fc565b600060405180830381865afa158015610601573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261062991908101906114e6565b600754604051632d2ac4c160e11b81529192506001600160a01b031690635a5589829061065a908490600401611621565b602060405180830381865afa158015610677573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061069b9190611662565b9250505090565b60006001600160a01b0383166106d35760405162461bcd60e51b81526004016106ca9061167b565b60405180910390fd5b604080518082018252600581526437bbb732b960d91b60208201526008549151630c3c20ed60e01b81526001600160a01b03878116600483015260009361072b93926103569290911690630c3c20ed9060240161030c565b60408051808201825260098152681c9958da5c1a595b9d60ba1b60208201526008549151630c3c20ed60e01b81526001600160a01b0388811660048301529394506000936107889361035692911690630c3c20ed9060240161030c565b905060006107ba60405180604001604052806006815260200165185b5bdd5b9d60d21b81525061035661035188611000565b905060006108346103f46040518060400160405280600d81526020016c7472616e736665725f66726f6d60981b8152506103566103f46108148989604051806040016040528060018152602001600b60fa1b815250611093565b87604051806040016040528060018152602001600b60fa1b815250611093565b905061083f8161110f565b50866001600160a01b0316886001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8860405161088591815260200190565b60405180910390a3506001979650505050505050565b6000806108e76103f46040518060400160405280600a815260200169746f6b656e5f696e666f60b01b815250604051806040016040528060028152602001617b7d60f01b815250610fcb565b6006546040516306d81d2960e01b81529192506000916001600160a01b03909116906306d81d29906109209060059086906004016115fc565b600060405180830381865afa15801561093d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261096591908101906114e6565b600754604051632d2ac4c160e11b81529192506001600160a01b031690635a5589829061065a9084906004016116be565b60006001600160a01b038216610a005760405162461bcd60e51b815260206004820152602960248201527f45524332303a2062616c616e636520717565727920666f7220746865207a65726044820152686f206164647265737360b81b60648201526084016106ca565b60408051808201825260078152666164647265737360c81b60208201526008549151630c3c20ed60e01b81526001600160a01b038581166004830152600093610a5a93926103569290911690630c3c20ed9060240161030c565b90506000610a8d6103f46040518060400160405280600781526020016662616c616e636560c81b815250610356856110df565b6006546040516306d81d2960e01b81529192506000916001600160a01b03909116906306d81d2990610ac69060059086906004016115fc565b600060405180830381865afa158015610ae3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610b0b91908101906114e6565b600754604051632d2ac4c160e11b81529192506001600160a01b031690635a55898290610b3c9084906004016116fb565b602060405180830381865afa158015610b59573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b7d9190611662565b95945050505050565b60606004805461020190611422565b60006001600160a01b038316610bbd5760405162461bcd60e51b81526004016106ca9061167b565b60408051808201825260098152681c9958da5c1a595b9d60ba1b60208201526008549151630c3c20ed60e01b81526001600160a01b038681166004830152600093610c1993926103569290911690630c3c20ed9060240161030c565b90506000610c4b60405180604001604052806006815260200165185b5bdd5b9d60d21b81525061035661035187611000565b90506000610c9d6103f4604051806040016040528060088152602001673a3930b739b332b960c11b8152506103566103f48787604051806040016040528060018152602001600b60fa1b815250611093565b9050610ca88161110f565b506040518581526001600160a01b0387169033907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a350600195945050505050565b60058054610d0290611422565b80601f0160208091040260200160405190810160405280929190818152602001828054610d2e90611422565b8015610d7b5780601f10610d5057610100808354040283529160200191610d7b565b820191906000526020600020905b815481529060010190602001808311610d5e57829003601f168201915b505050505081565b604080518082018252600581526437bbb732b960d91b60208201526008549151630c3c20ed60e01b81526001600160a01b0385811660048301526000938493610ddc939092610356921690630c3c20ed9060240161030c565b604080518082018252600781526639b832b73232b960c91b60208201526008549151630c3c20ed60e01b81526001600160a01b038781166004830152939450600093610e379361035692911690630c3c20ed9060240161030c565b90506000610e8a6103f460405180604001604052806009815260200168616c6c6f77616e636560b81b8152506103566103f48787604051806040016040528060018152602001600b60fa1b815250611093565b6006546040516306d81d2960e01b81529192506000916001600160a01b03909116906306d81d2990610ec39060059086906004016115fc565b600060405180830381865afa158015610ee0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610f0891908101906114e6565b600754604051632d2ac4c160e11b81529192506001600160a01b031690635a55898290610f39908490600401611737565b602060405180830381865afa158015610f56573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f7a9190611662565b979650505050505050565b606081604051602001610f989190611775565b60408051601f1981840301815290829052610fb59160200161179a565b6040516020818303038152906040529050919050565b6060610ff9610fd984610f85565b83604051806040016040528060018152602001601d60f91b815250611093565b9392505050565b6060600061100d83611217565b600101905060008167ffffffffffffffff81111561102d5761102d61145c565b6040519080825280601f01601f191660200182016040528015611057576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a850494508461106157509392505050565b60608382846040516020016110a99291906117c4565b60408051601f19818403018152908290526110c792916020016117c4565b60405160208183030381529060405290509392505050565b6060816040516020016110f291906117f3565b60408051601f1981840301815290829052610fb591602001611818565b60606000806110026001600160a01b0316600585604051806040016040528060028152602001615b5d60f01b81525060405160240161115093929190611834565b60408051601f198184030181529181526020820180516001600160e01b031663226913d760e11b179052516111859190611877565b600060405180830381855af49150503d80600081146111c0576040519150601f19603f3d011682016040523d82523d6000602084013e6111c5565b606091505b509150915081610ff95760405162461bcd60e51b815260206004820152601760248201527f436f736d5761736d2065786563757465206661696c656400000000000000000060448201526064016106ca565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b83106112565772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef81000000008310611282576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc1000083106112a057662386f26fc10000830492506010015b6305f5e10083106112b8576305f5e100830492506008015b61271083106112cc57612710830492506004015b606483106112de576064830492506002015b600a83106105595760010192915050565b60005b8381101561130a5781810151838201526020016112f2565b50506000910152565b6000815180845261132b8160208601602086016112ef565b601f01601f19169290920160200192915050565b602081526000610ff96020830184611313565b80356001600160a01b038116811461136957600080fd5b919050565b6000806040838503121561138157600080fd5b61138a83611352565b946020939093013593505050565b6000806000606084860312156113ad57600080fd5b6113b684611352565b92506113c460208501611352565b9150604084013590509250925092565b6000602082840312156113e657600080fd5b610ff982611352565b6000806040838503121561140257600080fd5b61140b83611352565b915061141960208401611352565b90509250929050565b600181811c9082168061143657607f821691505b60208210810361145657634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052604160045260246000fd5b600067ffffffffffffffff8084111561148d5761148d61145c565b604051601f8501601f19908116603f011681019082821181831017156114b5576114b561145c565b816040528093508581528686860111156114ce57600080fd5b6114dc8660208301876112ef565b5050509392505050565b6000602082840312156114f857600080fd5b815167ffffffffffffffff81111561150f57600080fd5b8201601f8101841361152057600080fd5b61152f84825160208401611472565b949350505050565b8181038181111561055957634e487b7160e01b600052601160045260246000fd5b8054600090600181811c908083168061157257607f831692505b6020808410820361159357634e487b7160e01b600052602260045260246000fd5b838852602088018280156115ae57600181146115c4576115ef565b60ff198716825285151560051b820197506115ef565b60008981526020902060005b878110156115e9578154848201529086019084016115d0565b83019850505b5050505050505092915050565b60408152600061160f6040830185611558565b8281036020840152610b7d8185611313565b6040815260006116346040830184611313565b8281036020840152600c81526b746f74616c5f737570706c7960a01b60208201526040810191505092915050565b60006020828403121561167457600080fd5b5051919050565b60208082526023908201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260408201526265737360e81b606082015260800190565b6040815260006116d16040830184611313565b82810360208401526008815267646563696d616c7360c01b60208201526040810191505092915050565b60408152600061170e6040830184611313565b8281036020840152600781526662616c616e636560c81b60208201526040810191505092915050565b60408152600061174a6040830184611313565b82810360208401526009815268616c6c6f77616e636560b81b60208201526040810191505092915050565b600082516117878184602087016112ef565b601160f91b920191825250600101919050565b601160f91b815281516000906117b78160018501602087016112ef565b9190910160010192915050565b600083516117d68184602088016112ef565b8351908301906117ea8183602088016112ef565b01949350505050565b600082516118058184602087016112ef565b607d60f81b920191825250600101919050565b607b60f81b8152600082516117b78160018501602087016112ef565b6060815260006118476060830186611558565b82810360208401526118598186611313565b9050828103604084015261186d8185611313565b9695505050505050565b600082516118898184602087016112ef565b919091019291505056fea2646970667358221220e64e858a1bdea2aaee103653f80a4a8a2bb0435883ac59876e1519653388ac8964736f6c63430008190033 \ No newline at end of file +608060405234801562000010575f80fd5b5060405162003240380380620032408339818101604052810190620000369190620002c4565b81818160039081620000499190620005b1565b5080600490816200005b9190620005b1565b50505061100260065f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061100360075f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061100460085f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508260059081620001359190620005b1565b5050505062000695565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620001a08262000158565b810181811067ffffffffffffffff82111715620001c257620001c162000168565b5b80604052505050565b5f620001d66200013f565b9050620001e4828262000195565b919050565b5f67ffffffffffffffff82111562000206576200020562000168565b5b620002118262000158565b9050602081019050919050565b5f5b838110156200023d57808201518184015260208101905062000220565b5f8484015250505050565b5f6200025e6200025884620001e9565b620001cb565b9050828152602081018484840111156200027d576200027c62000154565b5b6200028a8482856200021e565b509392505050565b5f82601f830112620002a957620002a862000150565b5b8151620002bb84826020860162000248565b91505092915050565b5f805f60608486031215620002de57620002dd62000148565b5b5f84015167ffffffffffffffff811115620002fe57620002fd6200014c565b5b6200030c8682870162000292565b935050602084015167ffffffffffffffff81111562000330576200032f6200014c565b5b6200033e8682870162000292565b925050604084015167ffffffffffffffff8111156200036257620003616200014c565b5b620003708682870162000292565b9150509250925092565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680620003c957607f821691505b602082108103620003df57620003de62000384565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620004437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000406565b6200044f868362000406565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f62000499620004936200048d8462000467565b62000470565b62000467565b9050919050565b5f819050919050565b620004b48362000479565b620004cc620004c382620004a0565b84845462000412565b825550505050565b5f90565b620004e2620004d4565b620004ef818484620004a9565b505050565b5b8181101562000516576200050a5f82620004d8565b600181019050620004f5565b5050565b601f82111562000565576200052f81620003e5565b6200053a84620003f7565b810160208510156200054a578190505b620005626200055985620003f7565b830182620004f4565b50505b505050565b5f82821c905092915050565b5f620005875f19846008026200056a565b1980831691505092915050565b5f620005a1838362000576565b9150826002028217905092915050565b620005bc826200037a565b67ffffffffffffffff811115620005d857620005d762000168565b5b620005e48254620003b1565b620005f18282856200051a565b5f60209050601f83116001811462000627575f841562000612578287015190505b6200061e858262000594565b8655506200068d565b601f1984166200063786620003e5565b5f5b82811015620006605784890151825560018201915060208501945060208101905062000639565b868310156200068057848901516200067c601f89168262000576565b8355505b6001600288020188555050505b505050505050565b612b9d80620006a35f395ff3fe608060405234801561000f575f80fd5b50600436106100cd575f3560e01c806395d89b411161008a578063da73d16b11610064578063da73d16b14610227578063dd62ed3e14610245578063de4725cc14610275578063f00b025514610293576100cd565b806395d89b41146101bb578063a9059cbb146101d9578063c2aed30214610209576100cd565b806306fdde03146100d1578063095ea7b3146100ef57806318160ddd1461011f57806323b872dd1461013d578063313ce5671461016d57806370a082311461018b575b5f80fd5b6100d96102b1565b6040516100e69190611e13565b60405180910390f35b61010960048036038101906101049190611ed1565b610341565b6040516101169190611f29565b60405180910390f35b610127610761565b6040516101349190611f51565b60405180910390f35b61015760048036038101906101529190611f6a565b610923565b6040516101649190611f29565b60405180910390f35b610175610c97565b6040516101829190611fd5565b60405180910390f35b6101a560048036038101906101a09190611fee565b610e59565b6040516101b29190611f51565b60405180910390f35b6101c3611144565b6040516101d09190611e13565b60405180910390f35b6101f360048036038101906101ee9190611ed1565b6111d4565b6040516102009190611f29565b60405180910390f35b610211611422565b60405161021e9190612074565b60405180910390f35b61022f611447565b60405161023c9190611e13565b60405180910390f35b61025f600480360381019061025a919061208d565b6114d3565b60405161026c9190611f51565b60405180910390f35b61027d611876565b60405161028a91906120eb565b60405180910390f35b61029b61189b565b6040516102a89190612124565b60405180910390f35b6060600380546102c09061216a565b80601f01602080910402602001604051908101604052809291908181526020018280546102ec9061216a565b80156103375780601f1061030e57610100808354040283529160200191610337565b820191905f5260205f20905b81548152906001019060200180831161031a57829003601f168201915b5050505050905090565b5f6fffffffffffffffffffffffffffffffff8016821115610372576fffffffffffffffffffffffffffffffff801691505b5f61037d33856114d3565b90508281111561056c575f61046a6040518060400160405280600781526020017f7370656e6465720000000000000000000000000000000000000000000000000081525061046560085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed896040518263ffffffff1660e01b815260040161041e91906121a9565b5f60405180830381865afa158015610438573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061046091906122e0565b6118c0565b611908565b90505f6104c76040518060400160405280600681526020017f616d6f756e7400000000000000000000000000000000000000000000000000008152506104c26104bd88876104b89190612354565b61195a565b6118c0565b611908565b90505f6105586105536040518060400160405280601281526020017f64656372656173655f616c6c6f77616e6365000000000000000000000000000081525061054e61054987876040518060400160405280600181526020017f2c00000000000000000000000000000000000000000000000000000000000000815250611a24565b611a72565b611908565b611a72565b905061056381611aba565b50505050610756565b82811015610755575f6106576040518060400160405280600781526020017f7370656e6465720000000000000000000000000000000000000000000000000081525061065260085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed896040518263ffffffff1660e01b815260040161060b91906121a9565b5f60405180830381865afa158015610625573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061064d91906122e0565b6118c0565b611908565b90505f6106b46040518060400160405280600681526020017f616d6f756e7400000000000000000000000000000000000000000000000000008152506106af6106aa86896106a59190612354565b61195a565b6118c0565b611908565b90505f6107456107406040518060400160405280601281526020017f696e6372656173655f616c6c6f77616e6365000000000000000000000000000081525061073b61073687876040518060400160405280600181526020017f2c00000000000000000000000000000000000000000000000000000000000000815250611a24565b611a72565b611908565b611a72565b905061075081611aba565b505050505b5b600191505092915050565b5f806107df6107da6040518060400160405280600a81526020017f746f6b656e5f696e666f000000000000000000000000000000000000000000008152506040518060400160405280600281526020017f7b7d000000000000000000000000000000000000000000000000000000000000815250611908565b611a72565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296005846040518363ffffffff1660e01b815260040161083f92919061246c565b5f60405180830381865afa158015610859573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610881919061253f565b905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635a558982826040518263ffffffff1660e01b81526004016108dd91906125d0565b602060405180830381865afa1580156108f8573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061091c9190612617565b9250505090565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610992576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610989906126b2565b60405180910390fd5b5f610a756040518060400160405280600581526020017f6f776e6572000000000000000000000000000000000000000000000000000000815250610a7060085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed896040518263ffffffff1660e01b8152600401610a2991906121a9565b5f60405180830381865afa158015610a43573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610a6b91906122e0565b6118c0565b611908565b90505f610b5a6040518060400160405280600981526020017f726563697069656e740000000000000000000000000000000000000000000000815250610b5560085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed896040518263ffffffff1660e01b8152600401610b0e91906121a9565b5f60405180830381865afa158015610b28573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610b5091906122e0565b6118c0565b611908565b90505f610bac6040518060400160405280600681526020017f616d6f756e740000000000000000000000000000000000000000000000000000815250610ba7610ba28861195a565b6118c0565b611908565b90505f610c7c610c776040518060400160405280600d81526020017f7472616e736665725f66726f6d00000000000000000000000000000000000000815250610c72610c6d610c3189896040518060400160405280600181526020017f2c00000000000000000000000000000000000000000000000000000000000000815250611a24565b876040518060400160405280600181526020017f2c00000000000000000000000000000000000000000000000000000000000000815250611a24565b611a72565b611908565b611a72565b9050610c8781611aba565b5060019450505050509392505050565b5f80610d15610d106040518060400160405280600a81526020017f746f6b656e5f696e666f000000000000000000000000000000000000000000008152506040518060400160405280600281526020017f7b7d000000000000000000000000000000000000000000000000000000000000815250611908565b611a72565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296005846040518363ffffffff1660e01b8152600401610d7592919061246c565b5f60405180830381865afa158015610d8f573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610db7919061253f565b905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635a558982826040518263ffffffff1660e01b8152600401610e13919061271a565b602060405180830381865afa158015610e2e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e529190612617565b9250505090565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610ec8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ebf906127bd565b60405180910390fd5b5f610fab6040518060400160405280600781526020017f6164647265737300000000000000000000000000000000000000000000000000815250610fa660085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b8152600401610f5f91906121a9565b5f60405180830381865afa158015610f79573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610fa191906122e0565b6118c0565b611908565b90505f610ffd610ff86040518060400160405280600781526020017f62616c616e636500000000000000000000000000000000000000000000000000815250610ff385611a72565b611908565b611a72565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296005846040518363ffffffff1660e01b815260040161105d92919061246c565b5f60405180830381865afa158015611077573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061109f919061253f565b905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635a558982826040518263ffffffff1660e01b81526004016110fb9190612825565b602060405180830381865afa158015611116573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061113a9190612617565b9350505050919050565b6060600480546111539061216a565b80601f016020809104026020016040519081016040528092919081815260200182805461117f9061216a565b80156111ca5780601f106111a1576101008083540402835291602001916111ca565b820191905f5260205f20905b8154815290600101906020018083116111ad57829003601f168201915b5050505050905090565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603611243576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161123a906126b2565b60405180910390fd5b5f6113266040518060400160405280600981526020017f726563697069656e74000000000000000000000000000000000000000000000081525061132160085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b81526004016112da91906121a9565b5f60405180830381865afa1580156112f4573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061131c91906122e0565b6118c0565b611908565b90505f6113786040518060400160405280600681526020017f616d6f756e74000000000000000000000000000000000000000000000000000081525061137361136e8761195a565b6118c0565b611908565b90505f6114096114046040518060400160405280600881526020017f7472616e736665720000000000000000000000000000000000000000000000008152506113ff6113fa87876040518060400160405280600181526020017f2c00000000000000000000000000000000000000000000000000000000000000815250611a24565b611a72565b611908565b611a72565b905061141481611aba565b506001935050505092915050565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600580546114549061216a565b80601f01602080910402602001604051908101604052809291908181526020018280546114809061216a565b80156114cb5780601f106114a2576101008083540402835291602001916114cb565b820191905f5260205f20905b8154815290600101906020018083116114ae57829003601f168201915b505050505081565b5f806115b76040518060400160405280600581526020017f6f776e65720000000000000000000000000000000000000000000000000000008152506115b260085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b815260040161156b91906121a9565b5f60405180830381865afa158015611585573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906115ad91906122e0565b6118c0565b611908565b90505f61169c6040518060400160405280600781526020017f7370656e6465720000000000000000000000000000000000000000000000000081525061169760085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b815260040161165091906121a9565b5f60405180830381865afa15801561166a573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061169291906122e0565b6118c0565b611908565b90505f61172d6117286040518060400160405280600981526020017f616c6c6f77616e6365000000000000000000000000000000000000000000000081525061172361171e87876040518060400160405280600181526020017f2c00000000000000000000000000000000000000000000000000000000000000815250611a24565b611a72565b611908565b611a72565b90505f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296005846040518363ffffffff1660e01b815260040161178d92919061246c565b5f60405180830381865afa1580156117a7573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906117cf919061253f565b905060075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635a558982826040518263ffffffff1660e01b815260040161182b91906128a2565b602060405180830381865afa158015611846573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061186a9190612617565b94505050505092915050565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6060816040516020016118d39190612935565b6040516020818303038152906040526040516020016118f2919061295a565b6040516020818303038152906040529050919050565b6060611952611916846118c0565b836040518060400160405280600181526020017f3a00000000000000000000000000000000000000000000000000000000000000815250611a24565b905092915050565b60605f600161196884611c38565b0190505f8167ffffffffffffffff811115611986576119856121ca565b5b6040519080825280601f01601f1916602001820160405280156119b85781602001600182028036833780820191505090505b5090505f82602001820190505b600115611a19578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a8581611a0e57611a0d61297f565b5b0494505f85036119c5575b819350505050919050565b6060838284604051602001611a3a9291906129ac565b604051602081830303815290604052604051602001611a5a9291906129ac565b60405160208183030381529060405290509392505050565b606081604051602001611a8591906129f5565b604051602081830303815290604052604051602001611aa49190612a40565b6040516020818303038152906040529050919050565b60605f8061100273ffffffffffffffffffffffffffffffffffffffff166005856040518060400160405280600281526020017f5b5d000000000000000000000000000000000000000000000000000000000000815250604051602401611b2293929190612a65565b6040516020818303038152906040527f44d227ae000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611bac9190612ae9565b5f60405180830381855af49150503d805f8114611be4576040519150601f19603f3d011682016040523d82523d5f602084013e611be9565b606091505b509150915081611c2e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c2590612b49565b60405180910390fd5b8092505050919050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310611c94577a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008381611c8a57611c8961297f565b5b0492506040810190505b6d04ee2d6d415b85acef81000000008310611cd1576d04ee2d6d415b85acef81000000008381611cc757611cc661297f565b5b0492506020810190505b662386f26fc100008310611d0057662386f26fc100008381611cf657611cf561297f565b5b0492506010810190505b6305f5e1008310611d29576305f5e1008381611d1f57611d1e61297f565b5b0492506008810190505b6127108310611d4e576127108381611d4457611d4361297f565b5b0492506004810190505b60648310611d715760648381611d6757611d6661297f565b5b0492506002810190505b600a8310611d80576001810190505b80915050919050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015611dc0578082015181840152602081019050611da5565b5f8484015250505050565b5f601f19601f8301169050919050565b5f611de582611d89565b611def8185611d93565b9350611dff818560208601611da3565b611e0881611dcb565b840191505092915050565b5f6020820190508181035f830152611e2b8184611ddb565b905092915050565b5f604051905090565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611e6d82611e44565b9050919050565b611e7d81611e63565b8114611e87575f80fd5b50565b5f81359050611e9881611e74565b92915050565b5f819050919050565b611eb081611e9e565b8114611eba575f80fd5b50565b5f81359050611ecb81611ea7565b92915050565b5f8060408385031215611ee757611ee6611e3c565b5b5f611ef485828601611e8a565b9250506020611f0585828601611ebd565b9150509250929050565b5f8115159050919050565b611f2381611f0f565b82525050565b5f602082019050611f3c5f830184611f1a565b92915050565b611f4b81611e9e565b82525050565b5f602082019050611f645f830184611f42565b92915050565b5f805f60608486031215611f8157611f80611e3c565b5b5f611f8e86828701611e8a565b9350506020611f9f86828701611e8a565b9250506040611fb086828701611ebd565b9150509250925092565b5f60ff82169050919050565b611fcf81611fba565b82525050565b5f602082019050611fe85f830184611fc6565b92915050565b5f6020828403121561200357612002611e3c565b5b5f61201084828501611e8a565b91505092915050565b5f819050919050565b5f61203c61203761203284611e44565b612019565b611e44565b9050919050565b5f61204d82612022565b9050919050565b5f61205e82612043565b9050919050565b61206e81612054565b82525050565b5f6020820190506120875f830184612065565b92915050565b5f80604083850312156120a3576120a2611e3c565b5b5f6120b085828601611e8a565b92505060206120c185828601611e8a565b9150509250929050565b5f6120d582612043565b9050919050565b6120e5816120cb565b82525050565b5f6020820190506120fe5f8301846120dc565b92915050565b5f61210e82612043565b9050919050565b61211e81612104565b82525050565b5f6020820190506121375f830184612115565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061218157607f821691505b6020821081036121945761219361213d565b5b50919050565b6121a381611e63565b82525050565b5f6020820190506121bc5f83018461219a565b92915050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61220082611dcb565b810181811067ffffffffffffffff8211171561221f5761221e6121ca565b5b80604052505050565b5f612231611e33565b905061223d82826121f7565b919050565b5f67ffffffffffffffff82111561225c5761225b6121ca565b5b61226582611dcb565b9050602081019050919050565b5f61228461227f84612242565b612228565b9050828152602081018484840111156122a05761229f6121c6565b5b6122ab848285611da3565b509392505050565b5f82601f8301126122c7576122c66121c2565b5b81516122d7848260208601612272565b91505092915050565b5f602082840312156122f5576122f4611e3c565b5b5f82015167ffffffffffffffff81111561231257612311611e40565b5b61231e848285016122b3565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61235e82611e9e565b915061236983611e9e565b925082820390508181111561238157612380612327565b5b92915050565b5f819050815f5260205f209050919050565b5f81546123a58161216a565b6123af8186611d93565b9450600182165f81146123c957600181146123df57612411565b60ff198316865281151560200286019350612411565b6123e885612387565b5f5b83811015612409578154818901526001820191506020810190506123ea565b808801955050505b50505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f61243e8261241a565b6124488185612424565b9350612458818560208601611da3565b61246181611dcb565b840191505092915050565b5f6040820190508181035f8301526124848185612399565b905081810360208301526124988184612434565b90509392505050565b5f67ffffffffffffffff8211156124bb576124ba6121ca565b5b6124c482611dcb565b9050602081019050919050565b5f6124e36124de846124a1565b612228565b9050828152602081018484840111156124ff576124fe6121c6565b5b61250a848285611da3565b509392505050565b5f82601f830112612526576125256121c2565b5b81516125368482602086016124d1565b91505092915050565b5f6020828403121561255457612553611e3c565b5b5f82015167ffffffffffffffff81111561257157612570611e40565b5b61257d84828501612512565b91505092915050565b7f746f74616c5f737570706c7900000000000000000000000000000000000000005f82015250565b5f6125ba600c83611d93565b91506125c582612586565b602082019050919050565b5f6040820190508181035f8301526125e88184612434565b905081810360208301526125fb816125ae565b905092915050565b5f8151905061261181611ea7565b92915050565b5f6020828403121561262c5761262b611e3c565b5b5f61263984828501612603565b91505092915050565b7f45524332303a207472616e7366657220746f20746865207a65726f20616464725f8201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b5f61269c602383611d93565b91506126a782612642565b604082019050919050565b5f6020820190508181035f8301526126c981612690565b9050919050565b7f646563696d616c730000000000000000000000000000000000000000000000005f82015250565b5f612704600883611d93565b915061270f826126d0565b602082019050919050565b5f6040820190508181035f8301526127328184612434565b90508181036020830152612745816126f8565b905092915050565b7f45524332303a2062616c616e636520717565727920666f7220746865207a65725f8201527f6f20616464726573730000000000000000000000000000000000000000000000602082015250565b5f6127a7602983611d93565b91506127b28261274d565b604082019050919050565b5f6020820190508181035f8301526127d48161279b565b9050919050565b7f62616c616e6365000000000000000000000000000000000000000000000000005f82015250565b5f61280f600783611d93565b915061281a826127db565b602082019050919050565b5f6040820190508181035f83015261283d8184612434565b9050818103602083015261285081612803565b905092915050565b7f616c6c6f77616e636500000000000000000000000000000000000000000000005f82015250565b5f61288c600983611d93565b915061289782612858565b602082019050919050565b5f6040820190508181035f8301526128ba8184612434565b905081810360208301526128cd81612880565b905092915050565b5f81905092915050565b5f6128e982611d89565b6128f381856128d5565b9350612903818560208601611da3565b80840191505092915050565b7f2200000000000000000000000000000000000000000000000000000000000000815250565b5f61294082846128df565b915061294b8261290f565b60018201915081905092915050565b5f6129648261290f565b60018201915061297482846128df565b915081905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6129b782856128df565b91506129c382846128df565b91508190509392505050565b7f7d00000000000000000000000000000000000000000000000000000000000000815250565b5f612a0082846128df565b9150612a0b826129cf565b60018201915081905092915050565b7f7b00000000000000000000000000000000000000000000000000000000000000815250565b5f612a4a82612a1a565b600182019150612a5a82846128df565b915081905092915050565b5f6060820190508181035f830152612a7d8186612399565b90508181036020830152612a918185612434565b90508181036040830152612aa58184612434565b9050949350505050565b5f81905092915050565b5f612ac38261241a565b612acd8185612aaf565b9350612add818560208601611da3565b80840191505092915050565b5f612af48284612ab9565b915081905092915050565b7f436f736d5761736d2065786563757465206661696c65640000000000000000005f82015250565b5f612b33601783611d93565b9150612b3e82612aff565b602082019050919050565b5f6020820190508181035f830152612b6081612b27565b905091905056fea2646970667358221220bbf61e425e456abc0922fd5419531658ae228bde2af551dc3a3ad2a297f4e7ea64736f6c63430008150033 \ No newline at end of file diff --git a/x/evm/artifacts/cw20/artifacts.go b/x/evm/artifacts/cw20/artifacts.go index 838c3ceb2..a62057271 100644 --- a/x/evm/artifacts/cw20/artifacts.go +++ b/x/evm/artifacts/cw20/artifacts.go @@ -13,7 +13,7 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/config" ) -const currentVersion uint16 = 1 +const currentVersion uint16 = 2 var versionOverride uint16 diff --git a/x/evm/artifacts/cw721/CW721ERC721Pointer.bin b/x/evm/artifacts/cw721/CW721ERC721Pointer.bin index 8c67fd57d..53a9a63c9 100644 --- a/x/evm/artifacts/cw721/CW721ERC721Pointer.bin +++ b/x/evm/artifacts/cw721/CW721ERC721Pointer.bin @@ -1 +1 @@ -608060405234801561000f575f80fd5b50604051614c2c380380614c2c8339818101604052810190610031919061027f565b8181815f90816100419190610530565b5080600190816100519190610530565b50505061100260095f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550611003600a5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550611004600b5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555082600890816101299190610530565b505050506105ff565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6101918261014b565b810181811067ffffffffffffffff821117156101b0576101af61015b565b5b80604052505050565b5f6101c2610132565b90506101ce8282610188565b919050565b5f67ffffffffffffffff8211156101ed576101ec61015b565b5b6101f68261014b565b9050602081019050919050565b8281835e5f83830152505050565b5f61022361021e846101d3565b6101b9565b90508281526020810184848401111561023f5761023e610147565b5b61024a848285610203565b509392505050565b5f82601f83011261026657610265610143565b5b8151610276848260208601610211565b91505092915050565b5f805f606084860312156102965761029561013b565b5b5f84015167ffffffffffffffff8111156102b3576102b261013f565b5b6102bf86828701610252565b935050602084015167ffffffffffffffff8111156102e0576102df61013f565b5b6102ec86828701610252565b925050604084015167ffffffffffffffff81111561030d5761030c61013f565b5b61031986828701610252565b9150509250925092565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061037157607f821691505b6020821081036103845761038361032d565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026103e67fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826103ab565b6103f086836103ab565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f61043461042f61042a84610408565b610411565b610408565b9050919050565b5f819050919050565b61044d8361041a565b6104616104598261043b565b8484546103b7565b825550505050565b5f90565b610475610469565b610480818484610444565b505050565b5b818110156104a3576104985f8261046d565b600181019050610486565b5050565b601f8211156104e8576104b98161038a565b6104c28461039c565b810160208510156104d1578190505b6104e56104dd8561039c565b830182610485565b50505b505050565b5f82821c905092915050565b5f6105085f19846008026104ed565b1980831691505092915050565b5f61052083836104f9565b9150826002028217905092915050565b61053982610323565b67ffffffffffffffff8111156105525761055161015b565b5b61055c825461035a565b6105678282856104a7565b5f60209050601f831160018114610598575f8415610586578287015190505b6105908582610515565b8655506105f7565b601f1984166105a68661038a565b5f5b828110156105cd578489015182556001820191506020850194506020810190506105a8565b868310156105ea57848901516105e6601f8916826104f9565b8355505b6001600288020188555050505b505050505050565b6146208061060c5f395ff3fe608060405234801561000f575f80fd5b5060043610610135575f3560e01c80635c4aead7116100b6578063b88d4fde1161007a578063b88d4fde14610372578063c2aed3021461038e578063c87b56dd146103ac578063de4725cc146103dc578063e985e9c5146103fa578063f00b02551461042a57610135565b80635c4aead7146102ba5780636352211e146102d857806370a082311461030857806395d89b4114610338578063a22cb4651461035657610135565b806323b872dd116100fd57806323b872dd146101f15780632a55205a1461020d5780632f745c591461023e57806342842e0e1461026e5780634f6ccce71461028a57610135565b806301ffc9a71461013957806306fdde0314610169578063081812fc14610187578063095ea7b3146101b757806318160ddd146101d3575b5f80fd5b610153600480360381019061014e9190612f31565b610448565b6040516101609190612f76565b60405180910390f35b6101716105e9565b60405161017e9190612fff565b60405180910390f35b6101a1600480360381019061019c9190613052565b610678565b6040516101ae91906130bc565b60405180910390f35b6101d160048036038101906101cc91906130ff565b6109d2565b005b6101db610c0e565b6040516101e8919061314c565b60405180910390f35b61020b60048036038101906102069190613165565b610d85565b005b610227600480360381019061022291906131b5565b6110a0565b6040516102359291906131f3565b60405180910390f35b610258600480360381019061025391906130ff565b6116d1565b604051610265919061314c565b60405180910390f35b61028860048036038101906102839190613165565b61170d565b005b6102a4600480360381019061029f9190613052565b61172c565b6040516102b1919061314c565b60405180910390f35b6102c2611768565b6040516102cf9190612fff565b60405180910390f35b6102f260048036038101906102ed9190613052565b6117f4565b6040516102ff91906130bc565b60405180910390f35b610322600480360381019061031d919061321a565b611a7e565b60405161032f919061314c565b60405180910390f35b610340611ef9565b60405161034d9190612fff565b60405180910390f35b610370600480360381019061036b919061326f565b611f89565b005b61038c600480360381019061038791906133d9565b61218c565b005b6103966121a9565b6040516103a391906134b4565b60405180910390f35b6103c660048036038101906103c19190613052565b6121ce565b6040516103d39190612fff565b60405180910390f35b6103e46123cb565b6040516103f191906134ed565b60405180910390f35b610414600480360381019061040f9190613506565b6123f0565b6040516104219190612f76565b60405180910390f35b610432612824565b60405161043f9190613564565b60405180910390f35b5f7f2a55205a000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061051257507f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061057a57507f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806105e257507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b9050919050565b60605f80546105f7906135aa565b80601f0160208091040260200160405190810160405280929190818152602001828054610623906135aa565b801561066e5780601f106106455761010080835404028352916020019161066e565b820191905f5260205f20905b81548152906001019060200180831161065157829003601f168201915b5050505050905090565b5f806106c96040518060400160405280600881526020017f746f6b656e5f69640000000000000000000000000000000000000000000000008152506106c46106bf86612849565b612913565b61295b565b90505f61071b6107166040518060400160405280600981526020017f617070726f76616c730000000000000000000000000000000000000000000000815250610711856129ad565b61295b565b6129ad565b90505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b815260040161077b9291906136bf565b5f60405180830381865afa158015610795573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906107bd9190613762565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621836040518263ffffffff1660e01b815260040161081a91906137f3565b5f60405180830381865afa158015610834573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061085c9190613908565b90505f815111156109c5575f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5835f815181106108b8576108b761394f565b5b60200260200101516040518263ffffffff1660e01b81526004016108dc91906139c6565b5f60405180830381865afa1580156108f6573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061091e9190613762565b9050600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539826040518263ffffffff1660e01b815260040161097a9190612fff565b602060405180830381865afa158015610995573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109b99190613a0d565b955050505050506109cd565b5f9450505050505b919050565b5f610ab56040518060400160405280600781526020017f7370656e64657200000000000000000000000000000000000000000000000000815250610ab0600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b8152600401610a6991906130bc565b5f60405180830381865afa158015610a83573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610aab9190613ad6565b612913565b61295b565b90505f610b076040518060400160405280600881526020017f746f6b656e5f6964000000000000000000000000000000000000000000000000815250610b02610afd86612849565b612913565b61295b565b90505f610b98610b936040518060400160405280600781526020017f617070726f766500000000000000000000000000000000000000000000000000815250610b8e610b8987876040518060400160405280600181526020017f2c000000000000000000000000000000000000000000000000000000000000008152506129f5565b6129ad565b61295b565b6129ad565b9050610ba381612a43565b50838573ffffffffffffffffffffffffffffffffffffffff16610bc5866117f4565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050505050565b5f8060095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d2960086040518060400160405280601181526020017f7b226e756d5f746f6b656e73223a7b7d7d0000000000000000000000000000008152506040518363ffffffff1660e01b8152600401610ca29291906136bf565b5f60405180830381865afa158015610cbc573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610ce49190613762565b9050600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635a558982826040518263ffffffff1660e01b8152600401610d409190613b67565b602060405180830381865afa158015610d5b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d7f9190613bae565b91505090565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610df5575f6040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610dec91906130bc565b60405180910390fd5b610dfe816117f4565b73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614610e6b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e6290613c23565b60405180910390fd5b5f610f4e6040518060400160405280600981526020017f726563697069656e740000000000000000000000000000000000000000000000815250610f49600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b8152600401610f0291906130bc565b5f60405180830381865afa158015610f1c573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610f449190613ad6565b612913565b61295b565b90505f610fa06040518060400160405280600881526020017f746f6b656e5f6964000000000000000000000000000000000000000000000000815250610f9b610f9686612849565b612913565b61295b565b90505f61103161102c6040518060400160405280600c81526020017f7472616e736665725f6e6674000000000000000000000000000000000000000081525061102761102287876040518060400160405280600181526020017f2c000000000000000000000000000000000000000000000000000000000000008152506129f5565b6129ad565b61295b565b6129ad565b905061103c81612a43565b50838573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4505050505050565b5f805f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d2960086040518060600160405280602c81526020016145bf602c91396040518363ffffffff1660e01b81526004016111189291906136bf565b5f60405180830381865afa158015611132573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061115a9190613762565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b81526004016111b79190613c8b565b5f60405180830381865afa1580156111d1573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906111f99190613762565b90507f6273151f959616268004b58dbb21e5c851b7b8d04498b4aabee12291d22fc034818051906020012014611264576040517f438c4bcb00000000000000000000000000000000000000000000000000000000815260040161125b90613d08565b60405180910390fd5b5f6112b46040518060400160405280600881526020017f746f6b656e5f69640000000000000000000000000000000000000000000000008152506112af6112aa8a612849565b612913565b61295b565b90505f6113066040518060400160405280600a81526020017f73616c655f7072696365000000000000000000000000000000000000000000008152506113016112fc8a612849565b612913565b61295b565b90505f6113976113926040518060400160405280600c81526020017f726f79616c74795f696e666f000000000000000000000000000000000000000081525061138d61138887876040518060400160405280600181526020017f2c000000000000000000000000000000000000000000000000000000000000008152506129f5565b6129ad565b61295b565b6129ad565b90505f6114276114226040518060400160405280600981526020017f657874656e73696f6e000000000000000000000000000000000000000000000081525061141d6114186040518060400160405280600381526020017f6d736700000000000000000000000000000000000000000000000000000000008152508761295b565b6129ad565b61295b565b6129ad565b90505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b81526004016114879291906136bf565b5f60405180830381865afa1580156114a1573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114c99190613762565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b81526004016115269190613d70565b5f60405180830381865afa158015611540573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906115689190613762565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635a558982846040518263ffffffff1660e01b81526004016115c59190613ded565b602060405180830381865afa1580156115e0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116049190613bae565b90505f825103611622575f819a509a505050505050505050506116ca565b600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539836040518263ffffffff1660e01b815260040161167c9190612fff565b602060405180830381865afa158015611697573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116bb9190613a0d565b819a509a505050505050505050505b9250929050565b5f6040517f74115a7400000000000000000000000000000000000000000000000000000000815260040161170490613e6a565b60405180910390fd5b61172783838360405180602001604052805f81525061218c565b505050565b5f6040517f74115a7400000000000000000000000000000000000000000000000000000000815260040161175f90613ed2565b60405180910390fd5b60088054611775906135aa565b80601f01602080910402602001604051908101604052809291908181526020018280546117a1906135aa565b80156117ec5780601f106117c3576101008083540402835291602001916117ec565b820191905f5260205f20905b8154815290600101906020018083116117cf57829003601f168201915b505050505081565b5f806118456040518060400160405280600881526020017f746f6b656e5f696400000000000000000000000000000000000000000000000081525061184061183b86612849565b612913565b61295b565b90505f6118976118926040518060400160405280600881526020017f6f776e65725f6f6600000000000000000000000000000000000000000000000081525061188d856129ad565b61295b565b6129ad565b90505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b81526004016118f79291906136bf565b5f60405180830381865afa158015611911573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906119399190613762565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b81526004016119969190613f3a565b5f60405180830381865afa1580156119b0573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906119d89190613762565b9050600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539826040518263ffffffff1660e01b8152600401611a349190612fff565b602060405180830381865afa158015611a4f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a739190613a0d565b945050505050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611aef575f6040517f89c62b64000000000000000000000000000000000000000000000000000000008152600401611ae691906130bc565b60405180910390fd5b5f60605f600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed866040518263ffffffff1660e01b8152600401611b4d91906130bc565b5f60405180830381865afa158015611b67573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611b8f9190613ad6565b604051602001611b9f9190613fcd565b604051602081830303815290604052604051602001611bbe9190614018565b60405160208183030381529060405290505f7fcc1a5a473fb6da314e591eaff47c9e633c2fdb385df3a7900f90f8a3ce756bdd905060605f8084604051602001611c089190614063565b604051602081830303815290604052604051602001611c2791906140ae565b60405160208183030381529060405290505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b8152600401611c969291906136bf565b5f60405180830381865afa158015611cb0573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611cd89190613762565b90505b84818051906020012014611ee957600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621826040518263ffffffff1660e01b8152600401611d43919061411d565b5f60405180830381865afa158015611d5d573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611d859190613908565b9350835192508288611d97919061417d565b975083600184611da791906141b0565b81518110611db857611db761394f565b5b6020026020010151604051602001611dd09190614209565b60405160208183030381529060405296508587604051602001611df492919061422e565b604051602081830303815290604052604051602001611e139190614063565b604051602081830303815290604052604051602001611e3291906140ae565b604051602081830303815290604052915060095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b8152600401611ea09291906136bf565b5f60405180830381865afa158015611eba573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611ee29190613762565b9050611cdb565b8798505050505050505050919050565b606060018054611f08906135aa565b80601f0160208091040260200160405190810160405280929190818152602001828054611f34906135aa565b8015611f7f5780601f10611f5657610100808354040283529160200191611f7f565b820191905f5260205f20905b815481529060010190602001808311611f6257829003601f168201915b5050505050905090565b5f61207461206f6040518060400160405280600881526020017f6f70657261746f7200000000000000000000000000000000000000000000000081525061206a600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b815260040161202391906130bc565b5f60405180830381865afa15801561203d573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906120659190613ad6565b612913565b61295b565b6129ad565b905081156120d1576120cb6120c66120c16040518060400160405280600b81526020017f617070726f76655f616c6c0000000000000000000000000000000000000000008152508461295b565b6129ad565b612a43565b50612122565b61212061211b6121166040518060400160405280600a81526020017f7265766f6b655f616c6c000000000000000000000000000000000000000000008152508461295b565b6129ad565b612a43565b505b8273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318460405161217f9190612f76565b60405180910390a3505050565b612197848484610d85565b6121a384848484612bc1565b50505050565b600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60606121d9826117f4565b505f61222a6040518060400160405280600881526020017f746f6b656e5f696400000000000000000000000000000000000000000000000081525061222561222086612849565b612913565b61295b565b90505f61227c6122776040518060400160405280600881526020017f6e66745f696e666f000000000000000000000000000000000000000000000000815250612272856129ad565b61295b565b6129ad565b90505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b81526004016122dc9291906136bf565b5f60405180830381865afa1580156122f6573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061231e9190613762565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b815260040161237b919061429b565b5f60405180830381865afa158015612395573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906123bd9190613762565b905080945050505050919050565b600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f806124d46040518060400160405280600581526020017f6f776e65720000000000000000000000000000000000000000000000000000008152506124cf600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b815260040161248891906130bc565b5f60405180830381865afa1580156124a2573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906124ca9190613ad6565b612913565b61295b565b90505f6125266125216040518060400160405280600d81526020017f616c6c5f6f70657261746f72730000000000000000000000000000000000000081525061251c856129ad565b61295b565b6129ad565b90505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b81526004016125869291906136bf565b5f60405180830381865afa1580156125a0573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906125c89190613762565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621836040518263ffffffff1660e01b81526004016126259190614318565b5f60405180830381865afa15801561263f573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906126679190613908565b90505f5b8151811015612815575f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e58484815181106126c5576126c461394f565b5b60200260200101516040518263ffffffff1660e01b81526004016126e991906139c6565b5f60405180830381865afa158015612703573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061272b9190613762565b90508773ffffffffffffffffffffffffffffffffffffffff16600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539836040518263ffffffff1660e01b815260040161279e9190612fff565b602060405180830381865afa1580156127b9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906127dd9190613a0d565b73ffffffffffffffffffffffffffffffffffffffff1603612807576001965050505050505061281e565b50808060010191505061266b565b505f9450505050505b92915050565b60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60605f600161285784612d73565b0190505f8167ffffffffffffffff811115612875576128746132b5565b5b6040519080825280601f01601f1916602001820160405280156128a75781602001600182028036833780820191505090505b5090505f82602001820190505b600115612908578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a85816128fd576128fc61434b565b5b0494505f85036128b4575b819350505050919050565b6060816040516020016129269190614018565b6040516020818303038152906040526040516020016129459190614378565b6040516020818303038152906040529050919050565b60606129a561296984612913565b836040518060400160405280600181526020017f3a000000000000000000000000000000000000000000000000000000000000008152506129f5565b905092915050565b6060816040516020016129c091906143c3565b6040516020818303038152906040526040516020016129df919061440e565b6040516020818303038152906040529050919050565b6060838284604051602001612a0b92919061422e565b604051602081830303815290604052604051602001612a2b92919061422e565b60405160208183030381529060405290509392505050565b60605f8061100273ffffffffffffffffffffffffffffffffffffffff166008856040518060400160405280600281526020017f5b5d000000000000000000000000000000000000000000000000000000000000815250604051602401612aab93929190614433565b6040516020818303038152906040527f44d227ae000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051612b3591906144b7565b5f60405180830381855af49150503d805f8114612b6d576040519150601f19603f3d011682016040523d82523d5f602084013e612b72565b606091505b509150915081612bb7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612bae90614517565b60405180910390fd5b8092505050919050565b5f8373ffffffffffffffffffffffffffffffffffffffff163b1115612d6d578273ffffffffffffffffffffffffffffffffffffffff1663150b7a02612c04612ec4565b8685856040518563ffffffff1660e01b8152600401612c269493929190614535565b6020604051808303815f875af1925050508015612c6157506040513d601f19601f82011682018060405250810190612c5e9190614593565b60015b612ce2573d805f8114612c8f576040519150601f19603f3d011682016040523d82523d5f602084013e612c94565b606091505b505f815103612cda57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401612cd191906130bc565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612d6b57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401612d6291906130bc565b60405180910390fd5b505b50505050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310612dcf577a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008381612dc557612dc461434b565b5b0492506040810190505b6d04ee2d6d415b85acef81000000008310612e0c576d04ee2d6d415b85acef81000000008381612e0257612e0161434b565b5b0492506020810190505b662386f26fc100008310612e3b57662386f26fc100008381612e3157612e3061434b565b5b0492506010810190505b6305f5e1008310612e64576305f5e1008381612e5a57612e5961434b565b5b0492506008810190505b6127108310612e89576127108381612e7f57612e7e61434b565b5b0492506004810190505b60648310612eac5760648381612ea257612ea161434b565b5b0492506002810190505b600a8310612ebb576001810190505b80915050919050565b5f33905090565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b612f1081612edc565b8114612f1a575f80fd5b50565b5f81359050612f2b81612f07565b92915050565b5f60208284031215612f4657612f45612ed4565b5b5f612f5384828501612f1d565b91505092915050565b5f8115159050919050565b612f7081612f5c565b82525050565b5f602082019050612f895f830184612f67565b92915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f612fd182612f8f565b612fdb8185612f99565b9350612feb818560208601612fa9565b612ff481612fb7565b840191505092915050565b5f6020820190508181035f8301526130178184612fc7565b905092915050565b5f819050919050565b6130318161301f565b811461303b575f80fd5b50565b5f8135905061304c81613028565b92915050565b5f6020828403121561306757613066612ed4565b5b5f6130748482850161303e565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6130a68261307d565b9050919050565b6130b68161309c565b82525050565b5f6020820190506130cf5f8301846130ad565b92915050565b6130de8161309c565b81146130e8575f80fd5b50565b5f813590506130f9816130d5565b92915050565b5f806040838503121561311557613114612ed4565b5b5f613122858286016130eb565b92505060206131338582860161303e565b9150509250929050565b6131468161301f565b82525050565b5f60208201905061315f5f83018461313d565b92915050565b5f805f6060848603121561317c5761317b612ed4565b5b5f613189868287016130eb565b935050602061319a868287016130eb565b92505060406131ab8682870161303e565b9150509250925092565b5f80604083850312156131cb576131ca612ed4565b5b5f6131d88582860161303e565b92505060206131e98582860161303e565b9150509250929050565b5f6040820190506132065f8301856130ad565b613213602083018461313d565b9392505050565b5f6020828403121561322f5761322e612ed4565b5b5f61323c848285016130eb565b91505092915050565b61324e81612f5c565b8114613258575f80fd5b50565b5f8135905061326981613245565b92915050565b5f806040838503121561328557613284612ed4565b5b5f613292858286016130eb565b92505060206132a38582860161325b565b9150509250929050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6132eb82612fb7565b810181811067ffffffffffffffff8211171561330a576133096132b5565b5b80604052505050565b5f61331c612ecb565b905061332882826132e2565b919050565b5f67ffffffffffffffff821115613347576133466132b5565b5b61335082612fb7565b9050602081019050919050565b828183375f83830152505050565b5f61337d6133788461332d565b613313565b905082815260208101848484011115613399576133986132b1565b5b6133a484828561335d565b509392505050565b5f82601f8301126133c0576133bf6132ad565b5b81356133d084826020860161336b565b91505092915050565b5f805f80608085870312156133f1576133f0612ed4565b5b5f6133fe878288016130eb565b945050602061340f878288016130eb565b93505060406134208782880161303e565b925050606085013567ffffffffffffffff81111561344157613440612ed8565b5b61344d878288016133ac565b91505092959194509250565b5f819050919050565b5f61347c6134776134728461307d565b613459565b61307d565b9050919050565b5f61348d82613462565b9050919050565b5f61349e82613483565b9050919050565b6134ae81613494565b82525050565b5f6020820190506134c75f8301846134a5565b92915050565b5f6134d782613483565b9050919050565b6134e7816134cd565b82525050565b5f6020820190506135005f8301846134de565b92915050565b5f806040838503121561351c5761351b612ed4565b5b5f613529858286016130eb565b925050602061353a858286016130eb565b9150509250929050565b5f61354e82613483565b9050919050565b61355e81613544565b82525050565b5f6020820190506135775f830184613555565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806135c157607f821691505b6020821081036135d4576135d361357d565b5b50919050565b5f819050815f5260205f209050919050565b5f81546135f8816135aa565b6136028186612f99565b9450600182165f811461361c576001811461363257613664565b60ff198316865281151560200286019350613664565b61363b856135da565b5f5b8381101561365c5781548189015260018201915060208101905061363d565b808801955050505b50505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f6136918261366d565b61369b8185613677565b93506136ab818560208601612fa9565b6136b481612fb7565b840191505092915050565b5f6040820190508181035f8301526136d781856135ec565b905081810360208301526136eb8184613687565b90509392505050565b5f6137066137018461332d565b613313565b905082815260208101848484011115613722576137216132b1565b5b61372d848285612fa9565b509392505050565b5f82601f830112613749576137486132ad565b5b81516137598482602086016136f4565b91505092915050565b5f6020828403121561377757613776612ed4565b5b5f82015167ffffffffffffffff81111561379457613793612ed8565b5b6137a084828501613735565b91505092915050565b7f617070726f76616c7300000000000000000000000000000000000000000000005f82015250565b5f6137dd600983612f99565b91506137e8826137a9565b602082019050919050565b5f6040820190508181035f83015261380b8184613687565b9050818103602083015261381e816137d1565b905092915050565b5f67ffffffffffffffff8211156138405761383f6132b5565b5b602082029050602081019050919050565b5f80fd5b5f61386761386284613826565b613313565b9050808382526020820190506020840283018581111561388a57613889613851565b5b835b818110156138d157805167ffffffffffffffff8111156138af576138ae6132ad565b5b8086016138bc8982613735565b8552602085019450505060208101905061388c565b5050509392505050565b5f82601f8301126138ef576138ee6132ad565b5b81516138ff848260208601613855565b91505092915050565b5f6020828403121561391d5761391c612ed4565b5b5f82015167ffffffffffffffff81111561393a57613939612ed8565b5b613946848285016138db565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f7370656e646572000000000000000000000000000000000000000000000000005f82015250565b5f6139b0600783612f99565b91506139bb8261397c565b602082019050919050565b5f6040820190508181035f8301526139de8184613687565b905081810360208301526139f1816139a4565b905092915050565b5f81519050613a07816130d5565b92915050565b5f60208284031215613a2257613a21612ed4565b5b5f613a2f848285016139f9565b91505092915050565b5f67ffffffffffffffff821115613a5257613a516132b5565b5b613a5b82612fb7565b9050602081019050919050565b5f613a7a613a7584613a38565b613313565b905082815260208101848484011115613a9657613a956132b1565b5b613aa1848285612fa9565b509392505050565b5f82601f830112613abd57613abc6132ad565b5b8151613acd848260208601613a68565b91505092915050565b5f60208284031215613aeb57613aea612ed4565b5b5f82015167ffffffffffffffff811115613b0857613b07612ed8565b5b613b1484828501613aa9565b91505092915050565b7f636f756e740000000000000000000000000000000000000000000000000000005f82015250565b5f613b51600583612f99565b9150613b5c82613b1d565b602082019050919050565b5f6040820190508181035f830152613b7f8184613687565b90508181036020830152613b9281613b45565b905092915050565b5f81519050613ba881613028565b92915050565b5f60208284031215613bc357613bc2612ed4565b5b5f613bd084828501613b9a565b91505092915050565b7f6066726f6d60206d75737420626520746865206f776e657200000000000000005f82015250565b5f613c0d601883612f99565b9150613c1882613bd9565b602082019050919050565b5f6020820190508181035f830152613c3a81613c01565b9050919050565b7f726f79616c74795f7061796d656e7473000000000000000000000000000000005f82015250565b5f613c75601083612f99565b9150613c8082613c41565b602082019050919050565b5f6040820190508181035f830152613ca38184613687565b90508181036020830152613cb681613c69565b905092915050565b7f726f79616c74795f696e666f00000000000000000000000000000000000000005f82015250565b5f613cf2600c83612f99565b9150613cfd82613cbe565b602082019050919050565b5f6020820190508181035f830152613d1f81613ce6565b9050919050565b7f61646472657373000000000000000000000000000000000000000000000000005f82015250565b5f613d5a600783612f99565b9150613d6582613d26565b602082019050919050565b5f6040820190508181035f830152613d888184613687565b90508181036020830152613d9b81613d4e565b905092915050565b7f726f79616c74795f616d6f756e740000000000000000000000000000000000005f82015250565b5f613dd7600e83612f99565b9150613de282613da3565b602082019050919050565b5f6040820190508181035f830152613e058184613687565b90508181036020830152613e1881613dcb565b905092915050565b7f746f6b656e4f664f776e65724279496e646578000000000000000000000000005f82015250565b5f613e54601383612f99565b9150613e5f82613e20565b602082019050919050565b5f6020820190508181035f830152613e8181613e48565b9050919050565b7f746f6b656e4279496e64657800000000000000000000000000000000000000005f82015250565b5f613ebc600c83612f99565b9150613ec782613e88565b602082019050919050565b5f6020820190508181035f830152613ee981613eb0565b9050919050565b7f6f776e65720000000000000000000000000000000000000000000000000000005f82015250565b5f613f24600583612f99565b9150613f2f82613ef0565b602082019050919050565b5f6040820190508181035f830152613f528184613687565b90508181036020830152613f6581613f18565b905092915050565b7f226c696d6974223a313030302c226f776e6572223a2200000000000000000000815250565b5f81905092915050565b5f613fa782612f8f565b613fb18185613f93565b9350613fc1818560208601612fa9565b80840191505092915050565b5f613fd782613f6d565b601682019150613fe78284613f9d565b915081905092915050565b7f2200000000000000000000000000000000000000000000000000000000000000815250565b5f6140238284613f9d565b915061402e82613ff2565b60018201915081905092915050565b7f7b22746f6b656e73223a7b000000000000000000000000000000000000000000815250565b5f61406d8261403d565b600b8201915061407d8284613f9d565b915081905092915050565b7f7d7d000000000000000000000000000000000000000000000000000000000000815250565b5f6140b98284613f9d565b91506140c482614088565b60028201915081905092915050565b7f746f6b656e7300000000000000000000000000000000000000000000000000005f82015250565b5f614107600683612f99565b9150614112826140d3565b602082019050919050565b5f6040820190508181035f8301526141358184613687565b90508181036020830152614148816140fb565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6141878261301f565b91506141928361301f565b92508282019050808211156141aa576141a9614150565b5b92915050565b5f6141ba8261301f565b91506141c58361301f565b92508282039050818111156141dd576141dc614150565b5b92915050565b7f2c2273746172745f6166746572223a0000000000000000000000000000000000815250565b5f614213826141e3565b600f820191506142238284613f9d565b915081905092915050565b5f6142398285613f9d565b91506142458284613f9d565b91508190509392505050565b7f746f6b656e5f75726900000000000000000000000000000000000000000000005f82015250565b5f614285600983612f99565b915061429082614251565b602082019050919050565b5f6040820190508181035f8301526142b38184613687565b905081810360208301526142c681614279565b905092915050565b7f6f70657261746f727300000000000000000000000000000000000000000000005f82015250565b5f614302600983612f99565b915061430d826142ce565b602082019050919050565b5f6040820190508181035f8301526143308184613687565b90508181036020830152614343816142f6565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f61438282613ff2565b6001820191506143928284613f9d565b915081905092915050565b7f7d00000000000000000000000000000000000000000000000000000000000000815250565b5f6143ce8284613f9d565b91506143d98261439d565b60018201915081905092915050565b7f7b00000000000000000000000000000000000000000000000000000000000000815250565b5f614418826143e8565b6001820191506144288284613f9d565b915081905092915050565b5f6060820190508181035f83015261444b81866135ec565b9050818103602083015261445f8185613687565b905081810360408301526144738184613687565b9050949350505050565b5f81905092915050565b5f6144918261366d565b61449b818561447d565b93506144ab818560208601612fa9565b80840191505092915050565b5f6144c28284614487565b915081905092915050565b7f436f736d5761736d2065786563757465206661696c65640000000000000000005f82015250565b5f614501601783612f99565b915061450c826144cd565b602082019050919050565b5f6020820190508181035f83015261452e816144f5565b9050919050565b5f6080820190506145485f8301876130ad565b61455560208301866130ad565b614562604083018561313d565b81810360608301526145748184613687565b905095945050505050565b5f8151905061458d81612f07565b92915050565b5f602082840312156145a8576145a7612ed4565b5b5f6145b58482850161457f565b9150509291505056fe7b22657874656e73696f6e223a7b226d7367223a7b22636865636b5f726f79616c74696573223a7b7d7d7d7da26469706673582212202cfa5745319dd924bc8557dfafdf484a8e400b4f43ea1d4904335aa5e8097f2c64736f6c63430008190033 \ No newline at end of file +608060405234801562000010575f80fd5b5060405162004c0638038062004c068339818101604052810190620000369190620002c3565b8181815f9081620000489190620005b0565b5080600190816200005a9190620005b0565b50505061100260095f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550611003600a5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550611004600b5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508260089081620001349190620005b0565b5050505062000694565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6200019f8262000157565b810181811067ffffffffffffffff82111715620001c157620001c062000167565b5b80604052505050565b5f620001d56200013e565b9050620001e3828262000194565b919050565b5f67ffffffffffffffff82111562000205576200020462000167565b5b620002108262000157565b9050602081019050919050565b5f5b838110156200023c5780820151818401526020810190506200021f565b5f8484015250505050565b5f6200025d6200025784620001e8565b620001ca565b9050828152602081018484840111156200027c576200027b62000153565b5b620002898482856200021d565b509392505050565b5f82601f830112620002a857620002a76200014f565b5b8151620002ba84826020860162000247565b91505092915050565b5f805f60608486031215620002dd57620002dc62000147565b5b5f84015167ffffffffffffffff811115620002fd57620002fc6200014b565b5b6200030b8682870162000291565b935050602084015167ffffffffffffffff8111156200032f576200032e6200014b565b5b6200033d8682870162000291565b925050604084015167ffffffffffffffff8111156200036157620003606200014b565b5b6200036f8682870162000291565b9150509250925092565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680620003c857607f821691505b602082108103620003de57620003dd62000383565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620004427fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000405565b6200044e868362000405565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f62000498620004926200048c8462000466565b6200046f565b62000466565b9050919050565b5f819050919050565b620004b38362000478565b620004cb620004c2826200049f565b84845462000411565b825550505050565b5f90565b620004e1620004d3565b620004ee818484620004a8565b505050565b5b818110156200051557620005095f82620004d7565b600181019050620004f4565b5050565b601f82111562000564576200052e81620003e4565b6200053984620003f6565b8101602085101562000549578190505b620005616200055885620003f6565b830182620004f3565b50505b505050565b5f82821c905092915050565b5f620005865f198460080262000569565b1980831691505092915050565b5f620005a0838362000575565b9150826002028217905092915050565b620005bb8262000379565b67ffffffffffffffff811115620005d757620005d662000167565b5b620005e38254620003b0565b620005f082828562000519565b5f60209050601f83116001811462000626575f841562000611578287015190505b6200061d858262000593565b8655506200068c565b601f1984166200063686620003e4565b5f5b828110156200065f5784890151825560018201915060208501945060208101905062000638565b868310156200067f57848901516200067b601f89168262000575565b8355505b6001600288020188555050505b505050505050565b61456480620006a25f395ff3fe608060405234801561000f575f80fd5b5060043610610135575f3560e01c80635c4aead7116100b6578063b88d4fde1161007a578063b88d4fde14610372578063c2aed3021461038e578063c87b56dd146103ac578063de4725cc146103dc578063e985e9c5146103fa578063f00b02551461042a57610135565b80635c4aead7146102ba5780636352211e146102d857806370a082311461030857806395d89b4114610338578063a22cb4651461035657610135565b806323b872dd116100fd57806323b872dd146101f15780632a55205a1461020d5780632f745c591461023e57806342842e0e1461026e5780634f6ccce71461028a57610135565b806301ffc9a71461013957806306fdde0314610169578063081812fc14610187578063095ea7b3146101b757806318160ddd146101d3575b5f80fd5b610153600480360381019061014e9190612e14565b610448565b6040516101609190612e59565b60405180910390f35b6101716105e9565b60405161017e9190612efc565b60405180910390f35b6101a1600480360381019061019c9190612f4f565b610678565b6040516101ae9190612fb9565b60405180910390f35b6101d160048036038101906101cc9190612ffc565b6109d2565b005b6101db610bab565b6040516101e89190613049565b60405180910390f35b61020b60048036038101906102069190613062565b610d22565b005b610227600480360381019061022291906130b2565b610fe2565b6040516102359291906130f0565b60405180910390f35b61025860048036038101906102539190612ffc565b611613565b6040516102659190613049565b60405180910390f35b61028860048036038101906102839190613062565b61164f565b005b6102a4600480360381019061029f9190612f4f565b61166e565b6040516102b19190613049565b60405180910390f35b6102c26116aa565b6040516102cf9190612efc565b60405180910390f35b6102f260048036038101906102ed9190612f4f565b611736565b6040516102ff9190612fb9565b60405180910390f35b610322600480360381019061031d9190613117565b6119c0565b60405161032f9190613049565b60405180910390f35b610340611e3b565b60405161034d9190612efc565b60405180910390f35b610370600480360381019061036b919061316c565b611ecb565b005b61038c600480360381019061038791906132d6565b612069565b005b610396612086565b6040516103a391906133b1565b60405180910390f35b6103c660048036038101906103c19190612f4f565b6120ab565b6040516103d39190612efc565b60405180910390f35b6103e46122a8565b6040516103f191906133ea565b60405180910390f35b610414600480360381019061040f9190613403565b6122cd565b6040516104219190612e59565b60405180910390f35b610432612707565b60405161043f9190613461565b60405180910390f35b5f7f2a55205a000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061051257507f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061057a57507f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806105e257507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b9050919050565b60605f80546105f7906134a7565b80601f0160208091040260200160405190810160405280929190818152602001828054610623906134a7565b801561066e5780601f106106455761010080835404028352916020019161066e565b820191905f5260205f20905b81548152906001019060200180831161065157829003601f168201915b5050505050905090565b5f806106c96040518060400160405280600881526020017f746f6b656e5f69640000000000000000000000000000000000000000000000008152506106c46106bf8661272c565b6127f6565b61283e565b90505f61071b6107166040518060400160405280600981526020017f617070726f76616c73000000000000000000000000000000000000000000000081525061071185612890565b61283e565b612890565b90505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b815260040161077b9291906135bc565b5f60405180830381865afa158015610795573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906107bd919061365f565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621836040518263ffffffff1660e01b815260040161081a91906136f0565b5f60405180830381865afa158015610834573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061085c9190613805565b90505f815111156109c5575f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5835f815181106108b8576108b761384c565b5b60200260200101516040518263ffffffff1660e01b81526004016108dc91906138c3565b5f60405180830381865afa1580156108f6573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061091e919061365f565b9050600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539826040518263ffffffff1660e01b815260040161097a9190612efc565b602060405180830381865afa158015610995573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109b9919061390a565b955050505050506109cd565b5f9450505050505b919050565b5f610ab56040518060400160405280600781526020017f7370656e64657200000000000000000000000000000000000000000000000000815250610ab0600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b8152600401610a699190612fb9565b5f60405180830381865afa158015610a83573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610aab91906139d3565b6127f6565b61283e565b90505f610b076040518060400160405280600881526020017f746f6b656e5f6964000000000000000000000000000000000000000000000000815250610b02610afd8661272c565b6127f6565b61283e565b90505f610b98610b936040518060400160405280600781526020017f617070726f766500000000000000000000000000000000000000000000000000815250610b8e610b8987876040518060400160405280600181526020017f2c000000000000000000000000000000000000000000000000000000000000008152506128d8565b612890565b61283e565b612890565b9050610ba381612926565b505050505050565b5f8060095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d2960086040518060400160405280601181526020017f7b226e756d5f746f6b656e73223a7b7d7d0000000000000000000000000000008152506040518363ffffffff1660e01b8152600401610c3f9291906135bc565b5f60405180830381865afa158015610c59573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610c81919061365f565b9050600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635a558982826040518263ffffffff1660e01b8152600401610cdd9190613a64565b602060405180830381865afa158015610cf8573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d1c9190613aab565b91505090565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610d92575f6040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610d899190612fb9565b60405180910390fd5b610d9b81611736565b73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614610e08576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dff90613b20565b60405180910390fd5b5f610eeb6040518060400160405280600981526020017f726563697069656e740000000000000000000000000000000000000000000000815250610ee6600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b8152600401610e9f9190612fb9565b5f60405180830381865afa158015610eb9573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610ee191906139d3565b6127f6565b61283e565b90505f610f3d6040518060400160405280600881526020017f746f6b656e5f6964000000000000000000000000000000000000000000000000815250610f38610f338661272c565b6127f6565b61283e565b90505f610fce610fc96040518060400160405280600c81526020017f7472616e736665725f6e66740000000000000000000000000000000000000000815250610fc4610fbf87876040518060400160405280600181526020017f2c000000000000000000000000000000000000000000000000000000000000008152506128d8565b612890565b61283e565b612890565b9050610fd981612926565b50505050505050565b5f805f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d2960086040518060600160405280602c8152602001614503602c91396040518363ffffffff1660e01b815260040161105a9291906135bc565b5f60405180830381865afa158015611074573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061109c919061365f565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b81526004016110f99190613b88565b5f60405180830381865afa158015611113573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061113b919061365f565b90507f6273151f959616268004b58dbb21e5c851b7b8d04498b4aabee12291d22fc0348180519060200120146111a6576040517f438c4bcb00000000000000000000000000000000000000000000000000000000815260040161119d90613c05565b60405180910390fd5b5f6111f66040518060400160405280600881526020017f746f6b656e5f69640000000000000000000000000000000000000000000000008152506111f16111ec8a61272c565b6127f6565b61283e565b90505f6112486040518060400160405280600a81526020017f73616c655f70726963650000000000000000000000000000000000000000000081525061124361123e8a61272c565b6127f6565b61283e565b90505f6112d96112d46040518060400160405280600c81526020017f726f79616c74795f696e666f00000000000000000000000000000000000000008152506112cf6112ca87876040518060400160405280600181526020017f2c000000000000000000000000000000000000000000000000000000000000008152506128d8565b612890565b61283e565b612890565b90505f6113696113646040518060400160405280600981526020017f657874656e73696f6e000000000000000000000000000000000000000000000081525061135f61135a6040518060400160405280600381526020017f6d736700000000000000000000000000000000000000000000000000000000008152508761283e565b612890565b61283e565b612890565b90505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b81526004016113c99291906135bc565b5f60405180830381865afa1580156113e3573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061140b919061365f565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b81526004016114689190613c6d565b5f60405180830381865afa158015611482573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114aa919061365f565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635a558982846040518263ffffffff1660e01b81526004016115079190613cea565b602060405180830381865afa158015611522573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115469190613aab565b90505f825103611564575f819a509a5050505050505050505061160c565b600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539836040518263ffffffff1660e01b81526004016115be9190612efc565b602060405180830381865afa1580156115d9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115fd919061390a565b819a509a505050505050505050505b9250929050565b5f6040517f74115a7400000000000000000000000000000000000000000000000000000000815260040161164690613d67565b60405180910390fd5b61166983838360405180602001604052805f815250612069565b505050565b5f6040517f74115a740000000000000000000000000000000000000000000000000000000081526004016116a190613dcf565b60405180910390fd5b600880546116b7906134a7565b80601f01602080910402602001604051908101604052809291908181526020018280546116e3906134a7565b801561172e5780601f106117055761010080835404028352916020019161172e565b820191905f5260205f20905b81548152906001019060200180831161171157829003601f168201915b505050505081565b5f806117876040518060400160405280600881526020017f746f6b656e5f696400000000000000000000000000000000000000000000000081525061178261177d8661272c565b6127f6565b61283e565b90505f6117d96117d46040518060400160405280600881526020017f6f776e65725f6f660000000000000000000000000000000000000000000000008152506117cf85612890565b61283e565b612890565b90505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b81526004016118399291906135bc565b5f60405180830381865afa158015611853573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061187b919061365f565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b81526004016118d89190613e37565b5f60405180830381865afa1580156118f2573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061191a919061365f565b9050600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539826040518263ffffffff1660e01b81526004016119769190612efc565b602060405180830381865afa158015611991573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119b5919061390a565b945050505050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611a31575f6040517f89c62b64000000000000000000000000000000000000000000000000000000008152600401611a289190612fb9565b60405180910390fd5b5f60605f600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed866040518263ffffffff1660e01b8152600401611a8f9190612fb9565b5f60405180830381865afa158015611aa9573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611ad191906139d3565b604051602001611ae19190613eca565b604051602081830303815290604052604051602001611b009190613f15565b60405160208183030381529060405290505f7fcc1a5a473fb6da314e591eaff47c9e633c2fdb385df3a7900f90f8a3ce756bdd905060605f8084604051602001611b4a9190613f60565b604051602081830303815290604052604051602001611b699190613fab565b60405160208183030381529060405290505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b8152600401611bd89291906135bc565b5f60405180830381865afa158015611bf2573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611c1a919061365f565b90505b84818051906020012014611e2b57600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621826040518263ffffffff1660e01b8152600401611c85919061401a565b5f60405180830381865afa158015611c9f573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611cc79190613805565b9350835192508288611cd9919061407a565b975083600184611ce991906140ad565b81518110611cfa57611cf961384c565b5b6020026020010151604051602001611d129190614106565b60405160208183030381529060405296508587604051602001611d3692919061412b565b604051602081830303815290604052604051602001611d559190613f60565b604051602081830303815290604052604051602001611d749190613fab565b604051602081830303815290604052915060095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b8152600401611de29291906135bc565b5f60405180830381865afa158015611dfc573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611e24919061365f565b9050611c1d565b8798505050505050505050919050565b606060018054611e4a906134a7565b80601f0160208091040260200160405190810160405280929190818152602001828054611e76906134a7565b8015611ec15780601f10611e9857610100808354040283529160200191611ec1565b820191905f5260205f20905b815481529060010190602001808311611ea457829003601f168201915b5050505050905090565b5f611fb6611fb16040518060400160405280600881526020017f6f70657261746f72000000000000000000000000000000000000000000000000815250611fac600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b8152600401611f659190612fb9565b5f60405180830381865afa158015611f7f573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611fa791906139d3565b6127f6565b61283e565b612890565b905081156120135761200d6120086120036040518060400160405280600b81526020017f617070726f76655f616c6c0000000000000000000000000000000000000000008152508461283e565b612890565b612926565b50612064565b61206261205d6120586040518060400160405280600a81526020017f7265766f6b655f616c6c000000000000000000000000000000000000000000008152508461283e565b612890565b612926565b505b505050565b612074848484610d22565b61208084848484612aa4565b50505050565b600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60606120b682611736565b505f6121076040518060400160405280600881526020017f746f6b656e5f69640000000000000000000000000000000000000000000000008152506121026120fd8661272c565b6127f6565b61283e565b90505f6121596121546040518060400160405280600881526020017f6e66745f696e666f00000000000000000000000000000000000000000000000081525061214f85612890565b61283e565b612890565b90505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b81526004016121b99291906135bc565b5f60405180830381865afa1580156121d3573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906121fb919061365f565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b81526004016122589190614198565b5f60405180830381865afa158015612272573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061229a919061365f565b905080945050505050919050565b600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f806123b16040518060400160405280600581526020017f6f776e65720000000000000000000000000000000000000000000000000000008152506123ac600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b81526004016123659190612fb9565b5f60405180830381865afa15801561237f573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906123a791906139d3565b6127f6565b61283e565b90505f6124036123fe6040518060400160405280600d81526020017f616c6c5f6f70657261746f7273000000000000000000000000000000000000008152506123f985612890565b61283e565b612890565b90505f60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296008846040518363ffffffff1660e01b81526004016124639291906135bc565b5f60405180830381865afa15801561247d573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906124a5919061365f565b90505f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621836040518263ffffffff1660e01b81526004016125029190614215565b5f60405180830381865afa15801561251c573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906125449190613805565b90505f5b81518110156126f8575f600a5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e58484815181106125a2576125a161384c565b5b60200260200101516040518263ffffffff1660e01b81526004016125c691906138c3565b5f60405180830381865afa1580156125e0573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190612608919061365f565b90508773ffffffffffffffffffffffffffffffffffffffff16600b5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539836040518263ffffffff1660e01b815260040161267b9190612efc565b602060405180830381865afa158015612696573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906126ba919061390a565b73ffffffffffffffffffffffffffffffffffffffff16036126e45760019650505050505050612701565b5080806126f090614248565b915050612548565b505f9450505050505b92915050565b60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60605f600161273a84612c56565b0190505f8167ffffffffffffffff811115612758576127576131b2565b5b6040519080825280601f01601f19166020018201604052801561278a5781602001600182028036833780820191505090505b5090505f82602001820190505b6001156127eb578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a85816127e0576127df61428f565b5b0494505f8503612797575b819350505050919050565b6060816040516020016128099190613f15565b60405160208183030381529060405260405160200161282891906142bc565b6040516020818303038152906040529050919050565b606061288861284c846127f6565b836040518060400160405280600181526020017f3a000000000000000000000000000000000000000000000000000000000000008152506128d8565b905092915050565b6060816040516020016128a39190614307565b6040516020818303038152906040526040516020016128c29190614352565b6040516020818303038152906040529050919050565b60608382846040516020016128ee92919061412b565b60405160208183030381529060405260405160200161290e92919061412b565b60405160208183030381529060405290509392505050565b60605f8061100273ffffffffffffffffffffffffffffffffffffffff166008856040518060400160405280600281526020017f5b5d00000000000000000000000000000000000000000000000000000000000081525060405160240161298e93929190614377565b6040516020818303038152906040527f44d227ae000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051612a1891906143fb565b5f60405180830381855af49150503d805f8114612a50576040519150601f19603f3d011682016040523d82523d5f602084013e612a55565b606091505b509150915081612a9a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612a919061445b565b60405180910390fd5b8092505050919050565b5f8373ffffffffffffffffffffffffffffffffffffffff163b1115612c50578273ffffffffffffffffffffffffffffffffffffffff1663150b7a02612ae7612da7565b8685856040518563ffffffff1660e01b8152600401612b099493929190614479565b6020604051808303815f875af1925050508015612b4457506040513d601f19601f82011682018060405250810190612b4191906144d7565b60015b612bc5573d805f8114612b72576040519150601f19603f3d011682016040523d82523d5f602084013e612b77565b606091505b505f815103612bbd57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401612bb49190612fb9565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612c4e57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401612c459190612fb9565b60405180910390fd5b505b50505050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310612cb2577a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008381612ca857612ca761428f565b5b0492506040810190505b6d04ee2d6d415b85acef81000000008310612cef576d04ee2d6d415b85acef81000000008381612ce557612ce461428f565b5b0492506020810190505b662386f26fc100008310612d1e57662386f26fc100008381612d1457612d1361428f565b5b0492506010810190505b6305f5e1008310612d47576305f5e1008381612d3d57612d3c61428f565b5b0492506008810190505b6127108310612d6c576127108381612d6257612d6161428f565b5b0492506004810190505b60648310612d8f5760648381612d8557612d8461428f565b5b0492506002810190505b600a8310612d9e576001810190505b80915050919050565b5f33905090565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b612df381612dbf565b8114612dfd575f80fd5b50565b5f81359050612e0e81612dea565b92915050565b5f60208284031215612e2957612e28612db7565b5b5f612e3684828501612e00565b91505092915050565b5f8115159050919050565b612e5381612e3f565b82525050565b5f602082019050612e6c5f830184612e4a565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015612ea9578082015181840152602081019050612e8e565b5f8484015250505050565b5f601f19601f8301169050919050565b5f612ece82612e72565b612ed88185612e7c565b9350612ee8818560208601612e8c565b612ef181612eb4565b840191505092915050565b5f6020820190508181035f830152612f148184612ec4565b905092915050565b5f819050919050565b612f2e81612f1c565b8114612f38575f80fd5b50565b5f81359050612f4981612f25565b92915050565b5f60208284031215612f6457612f63612db7565b5b5f612f7184828501612f3b565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f612fa382612f7a565b9050919050565b612fb381612f99565b82525050565b5f602082019050612fcc5f830184612faa565b92915050565b612fdb81612f99565b8114612fe5575f80fd5b50565b5f81359050612ff681612fd2565b92915050565b5f806040838503121561301257613011612db7565b5b5f61301f85828601612fe8565b925050602061303085828601612f3b565b9150509250929050565b61304381612f1c565b82525050565b5f60208201905061305c5f83018461303a565b92915050565b5f805f6060848603121561307957613078612db7565b5b5f61308686828701612fe8565b935050602061309786828701612fe8565b92505060406130a886828701612f3b565b9150509250925092565b5f80604083850312156130c8576130c7612db7565b5b5f6130d585828601612f3b565b92505060206130e685828601612f3b565b9150509250929050565b5f6040820190506131035f830185612faa565b613110602083018461303a565b9392505050565b5f6020828403121561312c5761312b612db7565b5b5f61313984828501612fe8565b91505092915050565b61314b81612e3f565b8114613155575f80fd5b50565b5f8135905061316681613142565b92915050565b5f806040838503121561318257613181612db7565b5b5f61318f85828601612fe8565b92505060206131a085828601613158565b9150509250929050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6131e882612eb4565b810181811067ffffffffffffffff82111715613207576132066131b2565b5b80604052505050565b5f613219612dae565b905061322582826131df565b919050565b5f67ffffffffffffffff821115613244576132436131b2565b5b61324d82612eb4565b9050602081019050919050565b828183375f83830152505050565b5f61327a6132758461322a565b613210565b905082815260208101848484011115613296576132956131ae565b5b6132a184828561325a565b509392505050565b5f82601f8301126132bd576132bc6131aa565b5b81356132cd848260208601613268565b91505092915050565b5f805f80608085870312156132ee576132ed612db7565b5b5f6132fb87828801612fe8565b945050602061330c87828801612fe8565b935050604061331d87828801612f3b565b925050606085013567ffffffffffffffff81111561333e5761333d612dbb565b5b61334a878288016132a9565b91505092959194509250565b5f819050919050565b5f61337961337461336f84612f7a565b613356565b612f7a565b9050919050565b5f61338a8261335f565b9050919050565b5f61339b82613380565b9050919050565b6133ab81613391565b82525050565b5f6020820190506133c45f8301846133a2565b92915050565b5f6133d482613380565b9050919050565b6133e4816133ca565b82525050565b5f6020820190506133fd5f8301846133db565b92915050565b5f806040838503121561341957613418612db7565b5b5f61342685828601612fe8565b925050602061343785828601612fe8565b9150509250929050565b5f61344b82613380565b9050919050565b61345b81613441565b82525050565b5f6020820190506134745f830184613452565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806134be57607f821691505b6020821081036134d1576134d061347a565b5b50919050565b5f819050815f5260205f209050919050565b5f81546134f5816134a7565b6134ff8186612e7c565b9450600182165f8114613519576001811461352f57613561565b60ff198316865281151560200286019350613561565b613538856134d7565b5f5b838110156135595781548189015260018201915060208101905061353a565b808801955050505b50505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f61358e8261356a565b6135988185613574565b93506135a8818560208601612e8c565b6135b181612eb4565b840191505092915050565b5f6040820190508181035f8301526135d481856134e9565b905081810360208301526135e88184613584565b90509392505050565b5f6136036135fe8461322a565b613210565b90508281526020810184848401111561361f5761361e6131ae565b5b61362a848285612e8c565b509392505050565b5f82601f830112613646576136456131aa565b5b81516136568482602086016135f1565b91505092915050565b5f6020828403121561367457613673612db7565b5b5f82015167ffffffffffffffff81111561369157613690612dbb565b5b61369d84828501613632565b91505092915050565b7f617070726f76616c7300000000000000000000000000000000000000000000005f82015250565b5f6136da600983612e7c565b91506136e5826136a6565b602082019050919050565b5f6040820190508181035f8301526137088184613584565b9050818103602083015261371b816136ce565b905092915050565b5f67ffffffffffffffff82111561373d5761373c6131b2565b5b602082029050602081019050919050565b5f80fd5b5f61376461375f84613723565b613210565b905080838252602082019050602084028301858111156137875761378661374e565b5b835b818110156137ce57805167ffffffffffffffff8111156137ac576137ab6131aa565b5b8086016137b98982613632565b85526020850194505050602081019050613789565b5050509392505050565b5f82601f8301126137ec576137eb6131aa565b5b81516137fc848260208601613752565b91505092915050565b5f6020828403121561381a57613819612db7565b5b5f82015167ffffffffffffffff81111561383757613836612dbb565b5b613843848285016137d8565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f7370656e646572000000000000000000000000000000000000000000000000005f82015250565b5f6138ad600783612e7c565b91506138b882613879565b602082019050919050565b5f6040820190508181035f8301526138db8184613584565b905081810360208301526138ee816138a1565b905092915050565b5f8151905061390481612fd2565b92915050565b5f6020828403121561391f5761391e612db7565b5b5f61392c848285016138f6565b91505092915050565b5f67ffffffffffffffff82111561394f5761394e6131b2565b5b61395882612eb4565b9050602081019050919050565b5f61397761397284613935565b613210565b905082815260208101848484011115613993576139926131ae565b5b61399e848285612e8c565b509392505050565b5f82601f8301126139ba576139b96131aa565b5b81516139ca848260208601613965565b91505092915050565b5f602082840312156139e8576139e7612db7565b5b5f82015167ffffffffffffffff811115613a0557613a04612dbb565b5b613a11848285016139a6565b91505092915050565b7f636f756e740000000000000000000000000000000000000000000000000000005f82015250565b5f613a4e600583612e7c565b9150613a5982613a1a565b602082019050919050565b5f6040820190508181035f830152613a7c8184613584565b90508181036020830152613a8f81613a42565b905092915050565b5f81519050613aa581612f25565b92915050565b5f60208284031215613ac057613abf612db7565b5b5f613acd84828501613a97565b91505092915050565b7f6066726f6d60206d75737420626520746865206f776e657200000000000000005f82015250565b5f613b0a601883612e7c565b9150613b1582613ad6565b602082019050919050565b5f6020820190508181035f830152613b3781613afe565b9050919050565b7f726f79616c74795f7061796d656e7473000000000000000000000000000000005f82015250565b5f613b72601083612e7c565b9150613b7d82613b3e565b602082019050919050565b5f6040820190508181035f830152613ba08184613584565b90508181036020830152613bb381613b66565b905092915050565b7f726f79616c74795f696e666f00000000000000000000000000000000000000005f82015250565b5f613bef600c83612e7c565b9150613bfa82613bbb565b602082019050919050565b5f6020820190508181035f830152613c1c81613be3565b9050919050565b7f61646472657373000000000000000000000000000000000000000000000000005f82015250565b5f613c57600783612e7c565b9150613c6282613c23565b602082019050919050565b5f6040820190508181035f830152613c858184613584565b90508181036020830152613c9881613c4b565b905092915050565b7f726f79616c74795f616d6f756e740000000000000000000000000000000000005f82015250565b5f613cd4600e83612e7c565b9150613cdf82613ca0565b602082019050919050565b5f6040820190508181035f830152613d028184613584565b90508181036020830152613d1581613cc8565b905092915050565b7f746f6b656e4f664f776e65724279496e646578000000000000000000000000005f82015250565b5f613d51601383612e7c565b9150613d5c82613d1d565b602082019050919050565b5f6020820190508181035f830152613d7e81613d45565b9050919050565b7f746f6b656e4279496e64657800000000000000000000000000000000000000005f82015250565b5f613db9600c83612e7c565b9150613dc482613d85565b602082019050919050565b5f6020820190508181035f830152613de681613dad565b9050919050565b7f6f776e65720000000000000000000000000000000000000000000000000000005f82015250565b5f613e21600583612e7c565b9150613e2c82613ded565b602082019050919050565b5f6040820190508181035f830152613e4f8184613584565b90508181036020830152613e6281613e15565b905092915050565b7f226c696d6974223a313030302c226f776e6572223a2200000000000000000000815250565b5f81905092915050565b5f613ea482612e72565b613eae8185613e90565b9350613ebe818560208601612e8c565b80840191505092915050565b5f613ed482613e6a565b601682019150613ee48284613e9a565b915081905092915050565b7f2200000000000000000000000000000000000000000000000000000000000000815250565b5f613f208284613e9a565b9150613f2b82613eef565b60018201915081905092915050565b7f7b22746f6b656e73223a7b000000000000000000000000000000000000000000815250565b5f613f6a82613f3a565b600b82019150613f7a8284613e9a565b915081905092915050565b7f7d7d000000000000000000000000000000000000000000000000000000000000815250565b5f613fb68284613e9a565b9150613fc182613f85565b60028201915081905092915050565b7f746f6b656e7300000000000000000000000000000000000000000000000000005f82015250565b5f614004600683612e7c565b915061400f82613fd0565b602082019050919050565b5f6040820190508181035f8301526140328184613584565b9050818103602083015261404581613ff8565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61408482612f1c565b915061408f83612f1c565b92508282019050808211156140a7576140a661404d565b5b92915050565b5f6140b782612f1c565b91506140c283612f1c565b92508282039050818111156140da576140d961404d565b5b92915050565b7f2c2273746172745f6166746572223a0000000000000000000000000000000000815250565b5f614110826140e0565b600f820191506141208284613e9a565b915081905092915050565b5f6141368285613e9a565b91506141428284613e9a565b91508190509392505050565b7f746f6b656e5f75726900000000000000000000000000000000000000000000005f82015250565b5f614182600983612e7c565b915061418d8261414e565b602082019050919050565b5f6040820190508181035f8301526141b08184613584565b905081810360208301526141c381614176565b905092915050565b7f6f70657261746f727300000000000000000000000000000000000000000000005f82015250565b5f6141ff600983612e7c565b915061420a826141cb565b602082019050919050565b5f6040820190508181035f83015261422d8184613584565b90508181036020830152614240816141f3565b905092915050565b5f61425282612f1c565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036142845761428361404d565b5b600182019050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6142c682613eef565b6001820191506142d68284613e9a565b915081905092915050565b7f7d00000000000000000000000000000000000000000000000000000000000000815250565b5f6143128284613e9a565b915061431d826142e1565b60018201915081905092915050565b7f7b00000000000000000000000000000000000000000000000000000000000000815250565b5f61435c8261432c565b60018201915061436c8284613e9a565b915081905092915050565b5f6060820190508181035f83015261438f81866134e9565b905081810360208301526143a38185613584565b905081810360408301526143b78184613584565b9050949350505050565b5f81905092915050565b5f6143d58261356a565b6143df81856143c1565b93506143ef818560208601612e8c565b80840191505092915050565b5f61440682846143cb565b915081905092915050565b7f436f736d5761736d2065786563757465206661696c65640000000000000000005f82015250565b5f614445601783612e7c565b915061445082614411565b602082019050919050565b5f6020820190508181035f83015261447281614439565b9050919050565b5f60808201905061448c5f830187612faa565b6144996020830186612faa565b6144a6604083018561303a565b81810360608301526144b88184613584565b905095945050505050565b5f815190506144d181612dea565b92915050565b5f602082840312156144ec576144eb612db7565b5b5f6144f9848285016144c3565b9150509291505056fe7b22657874656e73696f6e223a7b226d7367223a7b22636865636b5f726f79616c74696573223a7b7d7d7d7da2646970667358221220f23f66730b2e9f7f1adacf04644ee581995acf595c055e83129163cca97d8c8f64736f6c63430008150033 \ No newline at end of file diff --git a/x/evm/artifacts/cw721/artifacts.go b/x/evm/artifacts/cw721/artifacts.go index 38cb29b63..13bef4800 100644 --- a/x/evm/artifacts/cw721/artifacts.go +++ b/x/evm/artifacts/cw721/artifacts.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" ) -const CurrentVersion uint16 = 4 +const CurrentVersion uint16 = 5 //go:embed CW721ERC721Pointer.abi //go:embed CW721ERC721Pointer.bin diff --git a/x/evm/artifacts/erc20/artifacts.go b/x/evm/artifacts/erc20/artifacts.go index 9e964cbec..67e2952ce 100644 --- a/x/evm/artifacts/erc20/artifacts.go +++ b/x/evm/artifacts/erc20/artifacts.go @@ -2,7 +2,7 @@ package erc20 import "embed" -const CurrentVersion uint16 = 1 +const CurrentVersion uint16 = 2 //go:embed cwerc20.wasm var f embed.FS diff --git a/x/evm/artifacts/erc20/cwerc20.wasm b/x/evm/artifacts/erc20/cwerc20.wasm index aac641633..b6c821c19 100644 Binary files a/x/evm/artifacts/erc20/cwerc20.wasm and b/x/evm/artifacts/erc20/cwerc20.wasm differ diff --git a/x/evm/artifacts/erc721/artifacts.go b/x/evm/artifacts/erc721/artifacts.go index 04b51cf6b..0fb530fcb 100644 --- a/x/evm/artifacts/erc721/artifacts.go +++ b/x/evm/artifacts/erc721/artifacts.go @@ -2,7 +2,7 @@ package erc721 import "embed" -const CurrentVersion uint16 = 5 +const CurrentVersion uint16 = 6 //go:embed cwerc721.wasm var f embed.FS diff --git a/x/evm/artifacts/erc721/cwerc721.wasm b/x/evm/artifacts/erc721/cwerc721.wasm index a1cfac086..379ede8a8 100644 Binary files a/x/evm/artifacts/erc721/cwerc721.wasm and b/x/evm/artifacts/erc721/cwerc721.wasm differ diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go index 60cea369a..eaf73ea83 100644 --- a/x/evm/client/cli/tx.go +++ b/x/evm/client/cli/tx.go @@ -224,10 +224,10 @@ type Response struct { func CmdDeployContract() *cobra.Command { cmd := &cobra.Command{ - Use: "deploy [path to binary] --from= --gas-fee-cap= --gas-limt= --evm-rpc=", + Use: "deploy [path to binary] ([constructor payload hex]) --from= --gas-fee-cap= --gas-limt= --evm-rpc=", Short: "Deploy an EVM contract for binary at specified path", Long: "", - Args: cobra.ExactArgs(1), + Args: cobra.RangeArgs(1, 2), RunE: func(cmd *cobra.Command, args []string) (err error) { code, err := os.ReadFile(args[0]) if err != nil { @@ -265,6 +265,15 @@ func CmdDeployContract() *cobra.Command { txData.Value = utils.Big0 txData.Data = bz + if len(args) == 2 { + payload, err := hex.DecodeString(args[1]) + if err != nil { + return err + } + + txData.Data = append(txData.Data, payload...) + } + resp, err := sendTx(txData, rpc, key) if err != nil { return err diff --git a/x/evm/genesis.go b/x/evm/genesis.go index 7e3802b31..c90d3bf70 100644 --- a/x/evm/genesis.go +++ b/x/evm/genesis.go @@ -83,6 +83,91 @@ func ExportGenesis(ctx sdk.Context, k *keeper.Keeper) *types.GenesisState { return genesis } +// TODO: move to better location +var GENESIS_EXPORT_STREAM_SERIALIZED_LEN_MAX = 1000 + +func ExportGenesisStream(ctx sdk.Context, k *keeper.Keeper) <-chan *types.GenesisState { + ch := make(chan *types.GenesisState) + go func() { + genesis := types.DefaultGenesis() + genesis.Params = k.GetParams(ctx) + ch <- genesis + + k.IterateSeiAddressMapping(ctx, func(evmAddr common.Address, seiAddr sdk.AccAddress) bool { + var genesis types.GenesisState + genesis.Params = k.GetParams(ctx) + genesis.AddressAssociations = append(genesis.AddressAssociations, &types.AddressAssociation{ + SeiAddress: seiAddr.String(), + EthAddress: evmAddr.Hex(), + }) + ch <- &genesis + return false + }) + + k.IterateAllCode(ctx, func(addr common.Address, code []byte) bool { + var genesis types.GenesisState + genesis.Params = k.GetParams(ctx) + genesis.Codes = append(genesis.Codes, &types.Code{ + Address: addr.Hex(), + Code: code, + }) + ch <- &genesis + return false + }) + + k.IterateState(ctx, func(addr common.Address, key, val common.Hash) bool { + var genesis types.GenesisState + genesis.Params = k.GetParams(ctx) + genesis.States = append(genesis.States, &types.ContractState{ + Address: addr.Hex(), + Key: key[:], + Value: val[:], + }) + ch <- &genesis + return false + }) + + k.IterateAllNonces(ctx, func(addr common.Address, nonce uint64) bool { + var genesis types.GenesisState + genesis.Params = k.GetParams(ctx) + genesis.Nonces = append(genesis.Nonces, &types.Nonce{ + Address: addr.Hex(), + Nonce: nonce, + }) + ch <- &genesis + return false + }) + + for _, prefix := range [][]byte{ + types.ReceiptKeyPrefix, + types.BlockBloomPrefix, + types.TxHashesPrefix, + types.PointerRegistryPrefix, + types.PointerCWCodePrefix, + types.PointerReverseRegistryPrefix, + } { + genesis := types.DefaultGenesis() + genesis.Params = k.GetParams(ctx) + k.IterateAll(ctx, prefix, func(key, val []byte) bool { + genesis.Serialized = append(genesis.Serialized, &types.Serialized{ + Prefix: prefix, + Key: key, + Value: val, + }) + if len(genesis.Serialized) > GENESIS_EXPORT_STREAM_SERIALIZED_LEN_MAX { + ch <- genesis + genesis = types.DefaultGenesis() + genesis.Params = k.GetParams(ctx) + } + return false + }) + ch <- genesis + } + close(ch) + }() + return ch +} + // GetGenesisStateFromAppState returns x/evm GenesisState given raw application // genesis state. func GetGenesisStateFromAppState(cdc codec.JSONCodec, appState map[string]json.RawMessage) *types.GenesisState { diff --git a/x/evm/gov.go b/x/evm/gov.go index 508cc740d..a527b0278 100644 --- a/x/evm/gov.go +++ b/x/evm/gov.go @@ -6,13 +6,9 @@ import ( "math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" "github.com/sei-protocol/sei-chain/utils" - "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" "github.com/sei-protocol/sei-chain/x/evm/keeper" - "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/sei-protocol/sei-chain/x/evm/types" ) @@ -22,67 +18,14 @@ func HandleAddERCNativePointerProposalV2(ctx sdk.Context, k *keeper.Keeper, p *t // should always be the case given validation decimals = uint8(p.Decimals) } - constructorArguments := []interface{}{ - p.Token, p.Name, p.Symbol, decimals, - } - packedArgs, err := native.GetParsedABI().Pack("", constructorArguments...) - if err != nil { - logNativeV2Error(ctx, p, "pack arguments", err.Error()) - return err - } - bin := append(native.GetBin(), packedArgs...) - stateDB := state.NewDBImpl(ctx, k, false) - evmModuleAddress := k.GetEVMAddressOrDefault(ctx, k.AccountKeeper().GetModuleAddress(types.ModuleName)) - msg := core.Message{ - From: evmModuleAddress, - Nonce: stateDB.GetNonce(evmModuleAddress), - Value: utils.Big0, - GasLimit: math.MaxUint64, - GasPrice: utils.Big0, - GasFeeCap: utils.Big0, - GasTipCap: utils.Big0, - Data: bin, - SkipAccountChecks: true, - } - gp := core.GasPool(math.MaxUint64) - blockCtx, err := k.GetVMBlockContext(ctx, gp) - if err != nil { - logNativeV2Error(ctx, p, "get block context", err.Error()) - return err - } - cfg := types.DefaultChainConfig().EthereumConfig(k.ChainID(ctx)) - txCtx := core.NewEVMTxContext(&msg) - evmInstance := vm.NewEVM(*blockCtx, txCtx, stateDB, cfg, vm.Config{}) - st := core.NewStateTransition(evmInstance, &msg, &gp, true) - // TODO: retain existing contract address if any - res, err := st.TransitionDb() - if err != nil { - logNativeV2Error(ctx, p, "deploying pointer", err.Error()) - return err - } - if res.Err != nil { - logNativeV2Error(ctx, p, "deploying pointer (VM)", res.Err.Error()) - return res.Err - } else { - surplus, err := stateDB.Finalize() - if err != nil { - logNativeV2Error(ctx, p, "finalizing", err.Error()) + return k.RunWithOneOffEVMInstance( + ctx, func(e *vm.EVM) error { + _, _, err := k.UpsertERCNativePointer(ctx, e, math.MaxUint64, p.Token, utils.ERCMetadata{Name: p.Name, Symbol: p.Symbol, Decimals: decimals}) return err - } - if !surplus.IsZero() { - // not an error worth quiting for but should be logged - logNativeV2Error(ctx, p, "finalizing (surplus)", surplus.String()) - } - } - contractAddr := crypto.CreateAddress(msg.From, msg.Nonce) - if err := k.SetERC20NativePointer(ctx, p.Token, contractAddr); err != nil { - return err - } - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "native"), - sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, p.Token), - sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", native.CurrentVersion)))) - return nil + }, func(s1, s2 string) { + logNativeV2Error(ctx, p, s1, s2) + }, + ) } func logNativeV2Error(ctx sdk.Context, p *types.AddERCNativePointerProposalV2, step string, err string) { diff --git a/x/evm/integration_test.go b/x/evm/integration_test.go index 808de0fe7..577fbcb46 100644 --- a/x/evm/integration_test.go +++ b/x/evm/integration_test.go @@ -3,6 +3,7 @@ package evm_test import ( "bytes" "crypto/sha256" + "embed" "encoding/hex" "encoding/json" "fmt" @@ -16,6 +17,7 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" "github.com/sei-protocol/sei-chain/precompiles/pointer" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw721" @@ -26,6 +28,9 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ) +//go:embed pointer_abi.json +var f embed.FS + func TestERC2981PointerToCW2981(t *testing.T) { k := testkeeper.EVMTestApp.EvmKeeper ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()) @@ -65,8 +70,7 @@ func TestERC2981PointerToCW2981(t *testing.T) { testPrivHex := hex.EncodeToString(privKey.Bytes()) key, _ := crypto.HexToECDSA(testPrivHex) to := common.HexToAddress(pointer.PointerAddress) - abi, err := pointer.ABI() - require.Nil(t, err) + abi := pcommon.MustGetABI(f, "pointer_abi.json") data, err := abi.Pack("addCW721Pointer", cw2981Addr.String()) require.Nil(t, err) txData := ethtypes.LegacyTx{ @@ -98,12 +102,12 @@ func TestERC2981PointerToCW2981(t *testing.T) { require.True(t, exists) require.NotEmpty(t, pointerAddr) // call pointer to get royalty info - abi, err = cw721.Cw721MetaData.GetAbi() + cw721abi, err := cw721.Cw721MetaData.GetAbi() require.Nil(t, err) - data, err = abi.Pack("royaltyInfo", big.NewInt(1), big.NewInt(1000)) + data, err = cw721abi.Pack("royaltyInfo", big.NewInt(1), big.NewInt(1000)) require.Nil(t, err) txData = ethtypes.LegacyTx{ - Nonce: 2, + Nonce: 1, GasPrice: big.NewInt(1000000000), Gas: 1000000, To: &pointerAddr, @@ -126,7 +130,7 @@ func TestERC2981PointerToCW2981(t *testing.T) { require.Nil(t, typedTxData.Unmarshal(res.Data)) typedMsgData := types.MsgEVMTransactionResponse{} require.Nil(t, typedMsgData.Unmarshal(typedTxData.Data[0].Data)) - ret, err := abi.Unpack("royaltyInfo", typedMsgData.ReturnData) + ret, err := cw721abi.Unpack("royaltyInfo", typedMsgData.ReturnData) require.Nil(t, err) require.Equal(t, big.NewInt(10), ret[1].(*big.Int)) require.Equal(t, adminEvmAddr.Hex(), ret[0].(common.Address).Hex()) diff --git a/x/evm/keeper/address.go b/x/evm/keeper/address.go index ac043e33c..a9c946b8b 100644 --- a/x/evm/keeper/address.go +++ b/x/evm/keeper/address.go @@ -74,3 +74,13 @@ func (k *Keeper) IterateSeiAddressMapping(ctx sdk.Context, cb func(evmAddr commo } } } + +// A sdk.AccAddress may not receive funds from bank if it's the result of direct-casting +// from an EVM address AND the originating EVM address has already been associated with +// a true (i.e. derived from the same pubkey) sdk.AccAddress. +func (k *Keeper) CanAddressReceive(ctx sdk.Context, addr sdk.AccAddress) bool { + directCast := common.BytesToAddress(addr) // casting goes both directions since both address formats have 20 bytes + associatedAddr, isAssociated := k.GetSeiAddress(ctx, directCast) + // if the associated address is the cast address itself, allow the address to receive (e.g. EVM contract addresses) + return associatedAddr.Equals(addr) || !isAssociated // this means it's either a cast address that's not associated yet, or not a cast address at all. +} diff --git a/x/evm/keeper/address_test.go b/x/evm/keeper/address_test.go index 825dfb565..25f341cfa 100644 --- a/x/evm/keeper/address_test.go +++ b/x/evm/keeper/address_test.go @@ -4,6 +4,7 @@ import ( "bytes" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/stretchr/testify/require" ) @@ -50,3 +51,22 @@ func TestGetAddressOrDefault(t *testing.T) { defaultSeiAddr := k.GetSeiAddressOrDefault(ctx, evmAddr) require.True(t, bytes.Equal(defaultSeiAddr, evmAddr[:])) } + +func TestSendingToCastAddress(t *testing.T) { + a := keeper.EVMTestApp + ctx := a.GetContextForDeliverTx([]byte{}) + seiAddr, evmAddr := keeper.MockAddressPair() + castAddr := sdk.AccAddress(evmAddr[:]) + sourceAddr, _ := keeper.MockAddressPair() + require.Nil(t, a.BankKeeper.MintCoins(ctx, "evm", sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10))))) + require.Nil(t, a.BankKeeper.SendCoinsFromModuleToAccount(ctx, "evm", sourceAddr, sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(5))))) + amt := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1))) + require.Nil(t, a.BankKeeper.SendCoinsFromModuleToAccount(ctx, "evm", castAddr, amt)) + require.Nil(t, a.BankKeeper.SendCoins(ctx, sourceAddr, castAddr, amt)) + require.Nil(t, a.BankKeeper.SendCoinsAndWei(ctx, sourceAddr, castAddr, sdk.OneInt(), sdk.OneInt())) + + a.EvmKeeper.SetAddressMapping(ctx, seiAddr, evmAddr) + require.NotNil(t, a.BankKeeper.SendCoinsFromModuleToAccount(ctx, "evm", castAddr, amt)) + require.NotNil(t, a.BankKeeper.SendCoins(ctx, sourceAddr, castAddr, amt)) + require.NotNil(t, a.BankKeeper.SendCoinsAndWei(ctx, sourceAddr, castAddr, sdk.OneInt(), sdk.OneInt())) +} diff --git a/x/evm/keeper/balance.go b/x/evm/keeper/balance.go new file mode 100644 index 000000000..e9c218864 --- /dev/null +++ b/x/evm/keeper/balance.go @@ -0,0 +1,17 @@ +package keeper + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +func (k *Keeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress) *big.Int { + denom := k.GetBaseDenom(ctx) + allUsei := k.BankKeeper().GetBalance(ctx, addr, denom).Amount + lockedUsei := k.BankKeeper().LockedCoins(ctx, addr).AmountOf(denom) // LockedCoins doesn't use iterators + usei := allUsei.Sub(lockedUsei) + wei := k.BankKeeper().GetWeiBalance(ctx, addr) + return usei.Mul(state.SdkUseiToSweiMultiplier).Add(wei).BigInt() +} diff --git a/x/evm/keeper/code.go b/x/evm/keeper/code.go index be7d1fe20..ac69c4a1a 100644 --- a/x/evm/keeper/code.go +++ b/x/evm/keeper/code.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/types" ) @@ -30,13 +31,20 @@ func (k *Keeper) SetCode(ctx sdk.Context, addr common.Address, code []byte) { h := crypto.Keccak256Hash(code) k.PrefixStore(ctx, types.CodeHashKeyPrefix).Set(addr[:], h[:]) // set association with direct cast Sei address for the contract address - k.SetAddressMapping(ctx, k.GetSeiAddressOrDefault(ctx, addr), addr) + if _, ok := k.GetSeiAddress(ctx, addr); !ok { + k.SetAddressMapping(ctx, k.GetSeiAddressOrDefault(ctx, addr), addr) + } } func (k *Keeper) GetCodeHash(ctx sdk.Context, addr common.Address) common.Hash { store := k.PrefixStore(ctx, types.CodeHashKeyPrefix) bz := store.Get(addr[:]) if bz == nil { + // per Ethereum behavior, if an address has no code or balance, return Hash(0) + if k.GetBalance(ctx, k.GetSeiAddressOrDefault(ctx, addr)).Cmp(utils.Big0) == 0 { + return common.Hash{} + } + // if an address has no code but some balance, return EmptyCodeHash return ethtypes.EmptyCodeHash } return common.BytesToHash(bz) diff --git a/x/evm/keeper/code_test.go b/x/evm/keeper/code_test.go index e3563ea17..57ea70577 100644 --- a/x/evm/keeper/code_test.go +++ b/x/evm/keeper/code_test.go @@ -4,6 +4,7 @@ import ( "testing" 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/crypto" "github.com/sei-protocol/sei-chain/testutil/keeper" @@ -14,6 +15,10 @@ func TestCode(t *testing.T) { k, ctx := keeper.MockEVMKeeper() _, addr := keeper.MockAddressPair() + require.Equal(t, common.Hash{}, k.GetCodeHash(ctx, addr)) + + k.BankKeeper().MintCoins(ctx, "evm", sdk.NewCoins(sdk.NewCoin("usei", sdk.OneInt()))) + k.BankKeeper().SendCoinsFromModuleToAccount(ctx, "evm", sdk.AccAddress(addr[:]), sdk.NewCoins(sdk.NewCoin("usei", sdk.OneInt()))) require.Equal(t, ethtypes.EmptyCodeHash, k.GetCodeHash(ctx, addr)) require.Nil(t, k.GetCode(ctx, addr)) require.Equal(t, 0, k.GetCodeSize(ctx, addr)) diff --git a/x/evm/keeper/coinbase.go b/x/evm/keeper/coinbase.go index 378f4b9ca..39115f3bd 100644 --- a/x/evm/keeper/coinbase.go +++ b/x/evm/keeper/coinbase.go @@ -20,7 +20,8 @@ func (k *Keeper) GetFeeCollectorAddress(ctx sdk.Context) (common.Address, error) return *cache, nil } moduleAddr := k.accountKeeper.GetModuleAddress(authtypes.FeeCollectorName) - evmAddr, ok := k.GetEVMAddress(ctx, moduleAddr) + // we don't want to charge gas for this query, since it could cause non-determinism + evmAddr, ok := k.GetEVMAddress(ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)), moduleAddr) if !ok { return common.Address{}, errors.New("fee collector's EVM address not found") } diff --git a/x/evm/keeper/deferred.go b/x/evm/keeper/deferred.go index e8f930b52..06382b6f5 100644 --- a/x/evm/keeper/deferred.go +++ b/x/evm/keeper/deferred.go @@ -11,20 +11,23 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/types" ) -func (k *Keeper) GetEVMTxDeferredInfo(ctx sdk.Context) (res []*types.DeferredInfo) { +func (k *Keeper) GetAllEVMTxDeferredInfo(ctx sdk.Context) (res []*types.DeferredInfo) { store := prefix.NewStore(ctx.TransientStore(k.transientStoreKey), types.DeferredInfoPrefix) for txIdx, msg := range k.msgs { - if msg == nil { - continue - } txRes := k.txResults[txIdx] key := make([]byte, 8) binary.BigEndian.PutUint64(key, uint64(txIdx)) val := store.Get(key) if val == nil { + if msg == nil { + continue + } // this means the transaction got reverted during execution, either in ante handler // or due to a panic in msg server etx, _ := msg.AsTransaction() + if etx == nil { + panic("etx is nil for EVM DeferredInfo msg.AsTransaction(). This should never happen.") + } if txRes.Code == 0 { ctx.Logger().Error(fmt.Sprintf("transaction %s has code 0 but no deferred info", etx.Hash().Hex())) } @@ -63,3 +66,18 @@ func (k *Keeper) AppendToEvmTxDeferredInfo(ctx sdk.Context, bloom ethtypes.Bloom } prefix.NewStore(ctx.TransientStore(k.transientStoreKey), types.DeferredInfoPrefix).Set(key, bz) } + +func (k *Keeper) GetEVMTxDeferredInfo(ctx sdk.Context) (*types.DeferredInfo, bool) { + key := make([]byte, 8) + binary.BigEndian.PutUint64(key, uint64(ctx.TxIndex())) + val := &types.DeferredInfo{} + bz := prefix.NewStore(ctx.TransientStore(k.transientStoreKey), types.DeferredInfoPrefix).Get(key) + if bz == nil { + return nil, false + } + if err := val.Unmarshal(bz); err != nil { + ctx.Logger().Error(fmt.Sprintf("failed to unmarshal EVM deferred info: %s", err)) + return nil, false + } + return val, true +} diff --git a/x/evm/keeper/evm.go b/x/evm/keeper/evm.go index dc1c5363d..97695dc14 100644 --- a/x/evm/keeper/evm.go +++ b/x/evm/keeper/evm.go @@ -114,7 +114,17 @@ func (k *Keeper) CallEVM(ctx sdk.Context, from common.Address, to *common.Addres if err != nil { return nil, err } - k.AppendToEvmTxDeferredInfo(ctx, ethtypes.Bloom{}, ethtypes.EmptyTxsHash, surplus) + vmErr := "" + if res.Err != nil { + vmErr = res.Err.Error() + } + receipt, err := k.WriteReceipt(ctx, stateDB, evmMsg, ethtypes.LegacyTxType, ctx.TxSum(), res.UsedGas, vmErr) + if err != nil { + return nil, err + } + bloom := ethtypes.Bloom{} + bloom.SetBytes(receipt.LogsBloom) + k.AppendToEvmTxDeferredInfo(ctx, bloom, ctx.TxSum(), surplus) return res.ReturnData, nil } diff --git a/x/evm/keeper/evm_test.go b/x/evm/keeper/evm_test.go index 950212eb9..afd667b6e 100644 --- a/x/evm/keeper/evm_test.go +++ b/x/evm/keeper/evm_test.go @@ -25,7 +25,7 @@ func TestInternalCallCreateContract(t *testing.T) { contractData := append(bytecode, args...) k := testkeeper.EVMTestApp.EvmKeeper - ctx := testkeeper.EVMTestApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + ctx := testkeeper.EVMTestApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2).WithTxSum([32]byte{1, 2, 3}) testAddr, _ := testkeeper.MockAddressPair() amt := sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(200000000))) require.Nil(t, k.BankKeeper().MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(200000000))))) @@ -41,6 +41,9 @@ func TestInternalCallCreateContract(t *testing.T) { ctx = ctx.WithIsEVM(false) _, err = k.HandleInternalEVMCall(ctx, req) require.Nil(t, err) + receipt, err := k.GetTransientReceipt(ctx, [32]byte{1, 2, 3}) + require.Nil(t, err) + require.NotNil(t, receipt) } func TestInternalCall(t *testing.T) { diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 895baf7b5..889f1c167 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -211,7 +211,7 @@ func (k *Keeper) GetVMBlockContext(ctx sdk.Context, gp core.GasPool) (*vm.BlockC Time: uint64(ctx.BlockHeader().Time.Unix()), Difficulty: utils.Big0, // only needed for PoW BaseFee: k.GetBaseFeePerGas(ctx).TruncateInt().BigInt(), // feemarket not enabled - BlobBaseFee: utils.Big0, // Cancun not enabled + BlobBaseFee: utils.Big1, // Cancun not enabled Random: &rh, }, nil } diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 90de9a83b..f4b2727da 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -215,7 +215,7 @@ func TestDeferredInfo(t *testing.T) { k.SetTxResults([]*abci.ExecTxResult{{Code: 0}, {Code: 0}, {Code: 0}, {Code: 1, Log: "test error"}}) msg := mockEVMTransactionMessage(t) k.SetMsgs([]*types.MsgEVMTransaction{nil, {}, {}, msg}) - infoList := k.GetEVMTxDeferredInfo(ctx) + infoList := k.GetAllEVMTxDeferredInfo(ctx) require.Equal(t, 3, len(infoList)) require.Equal(t, uint32(1), infoList[0].TxIndex) require.Equal(t, ethtypes.Bloom{1, 2, 3}, ethtypes.BytesToBloom(infoList[0].TxBloom)) @@ -235,7 +235,7 @@ func TestDeferredInfo(t *testing.T) { a.Commit(context.Background()) // commit would clear transient stores k.SetTxResults([]*abci.ExecTxResult{}) k.SetMsgs([]*types.MsgEVMTransaction{}) - infoList = k.GetEVMTxDeferredInfo(ctx) + infoList = k.GetAllEVMTxDeferredInfo(ctx) require.Empty(t, len(infoList)) } diff --git a/x/evm/keeper/log.go b/x/evm/keeper/log.go index c7012780c..b7078759f 100644 --- a/x/evm/keeper/log.go +++ b/x/evm/keeper/log.go @@ -58,3 +58,9 @@ func ConvertEthLog(l *ethtypes.Log) *types.Log { Index: uint32(l.Index), } } + +func ConvertSyntheticEthLog(l *ethtypes.Log) *types.Log { + log := ConvertEthLog(l) + log.Synthetic = true + return log +} diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index adb8331a2..17bb8675d 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -22,7 +22,6 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc20" "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc721" "github.com/sei-protocol/sei-chain/x/evm/state" @@ -64,6 +63,7 @@ func (server msgServer) EVMTransaction(goCtx context.Context, msg *types.MsgEVMT gp := server.GetGasPool() defer func() { + defer stateDB.Cleanup() if pe := recover(); pe != nil { if !strings.Contains(fmt.Sprintf("%s", pe), occtypes.ErrReadEstimate.Error()) { debug.PrintStack() @@ -84,7 +84,7 @@ func (server msgServer) EVMTransaction(goCtx context.Context, msg *types.MsgEVMT ) return } - receipt, rerr := server.writeReceipt(ctx, msg, tx, emsg, serverRes, stateDB) + receipt, rerr := server.WriteReceipt(ctx, stateDB, emsg, uint32(tx.Type()), tx.Hash(), serverRes.GasUsed, serverRes.VmError) if rerr != nil { err = rerr ctx.Logger().Error(fmt.Sprintf("failed to write EVM receipt: %s", err)) @@ -280,51 +280,6 @@ func (k *Keeper) applyEVMMessageWithTracing( return st.TransitionDb() } -func (server msgServer) writeReceipt(ctx sdk.Context, origMsg *types.MsgEVMTransaction, tx *ethtypes.Transaction, msg *core.Message, response *types.MsgEVMTransactionResponse, stateDB *state.DBImpl) (*types.Receipt, error) { - ethLogs := stateDB.GetAllLogs() - bloom := ethtypes.CreateBloom(ethtypes.Receipts{ðtypes.Receipt{Logs: ethLogs}}) - receipt := &types.Receipt{ - TxType: uint32(tx.Type()), - CumulativeGasUsed: uint64(0), - TxHashHex: tx.Hash().Hex(), - GasUsed: response.GasUsed, - BlockNumber: uint64(ctx.BlockHeight()), - TransactionIndex: uint32(ctx.TxIndex()), - EffectiveGasPrice: tx.GasPrice().Uint64(), - VmError: response.VmError, - Logs: utils.Map(ethLogs, ConvertEthLog), - LogsBloom: bloom[:], - } - - if msg.To == nil { - receipt.ContractAddress = crypto.CreateAddress(msg.From, msg.Nonce).Hex() - } else { - receipt.To = msg.To.Hex() - if len(msg.Data) > 0 { - receipt.ContractAddress = msg.To.Hex() - } - } - - if response.VmError == "" { - receipt.Status = uint32(ethtypes.ReceiptStatusSuccessful) - } else { - receipt.Status = uint32(ethtypes.ReceiptStatusFailed) - } - - if perr := stateDB.GetPrecompileError(); perr != nil { - if receipt.Status > 0 { - ctx.Logger().Error(fmt.Sprintf("Transaction %s succeeded in execution but has precompile error %s", receipt.TxHashHex, perr.Error())) - } else { - // append precompile error to VM error - receipt.VmError = fmt.Sprintf("%s|%s", receipt.VmError, perr.Error()) - } - } - - receipt.From = origMsg.Derived.SenderEVMAddr.Hex() - - return receipt, server.SetTransientReceipt(ctx, tx.Hash(), receipt) -} - func (server msgServer) Send(goCtx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) recipient := server.GetSeiAddressOrDefault(ctx, common.HexToAddress(msg.ToAddress)) @@ -368,12 +323,20 @@ func (server msgServer) RegisterPointer(goCtx context.Context, msg *types.MsgReg panic("unknown pointer type") } codeID := server.GetStoredPointerCodeID(ctx, msg.PointerType) - bz, err := json.Marshal(payload) - if err != nil { - return nil, err - } moduleAcct := server.accountKeeper.GetModuleAddress(types.ModuleName) - pointerAddr, _, err := server.wasmKeeper.Instantiate(ctx, codeID, moduleAcct, moduleAcct, bz, fmt.Sprintf("Pointer of %s", msg.ErcAddress), sdk.NewCoins()) + var err error + var pointerAddr sdk.AccAddress + if exists { + bz, _ := json.Marshal(map[string]interface{}{}) + pointerAddr = existingPointer + _, err = server.wasmKeeper.Migrate(ctx, existingPointer, moduleAcct, codeID, bz) + } else { + bz, jerr := json.Marshal(payload) + if jerr != nil { + return nil, jerr + } + pointerAddr, _, err = server.wasmKeeper.Instantiate(ctx, codeID, moduleAcct, moduleAcct, bz, fmt.Sprintf("Pointer of %s", msg.ErcAddress), sdk.NewCoins()) + } if err != nil { return nil, err } diff --git a/x/evm/keeper/msg_server_test.go b/x/evm/keeper/msg_server_test.go index 7a96a68f5..f8663da65 100644 --- a/x/evm/keeper/msg_server_test.go +++ b/x/evm/keeper/msg_server_test.go @@ -11,6 +11,7 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/ethereum/go-ethereum/common" @@ -602,6 +603,21 @@ func TestRegisterPointer(t *testing.T) { hasRegisteredEvent = true } require.False(t, hasRegisteredEvent) + + // upgrade + k.DeleteCW721ERC721Pointer(ctx, pointee, version) + k.SetCW721ERC721PointerWithVersion(ctx, pointee, pointer.String(), version-1) + res, err = keeper.NewMsgServerImpl(k).RegisterPointer(sdk.WrapSDKContext(ctx), &types.MsgRegisterPointer{ + Sender: sender.String(), + PointerType: types.PointerType_ERC721, + ErcAddress: pointee.Hex(), + }) + require.Nil(t, err) + newPointer, version, exists := k.GetCW721ERC721Pointer(ctx, pointee) + require.True(t, exists) + require.Equal(t, erc721.CurrentVersion, version) + require.Equal(t, newPointer.String(), res.PointerAddress) + require.Equal(t, newPointer.String(), pointer.String()) // should retain the existing contract address } func TestEvmError(t *testing.T) { @@ -679,8 +695,8 @@ func TestEvmError(t *testing.T) { require.Nil(t, err) res = testkeeper.EVMTestApp.DeliverTx(ctx, abci.RequestDeliverTx{Tx: txbz}, sdktx, sha256.Sum256(txbz)) - require.Equal(t, uint32(45), res.Code) require.NoError(t, k.FlushTransientReceipts(ctx)) + require.Equal(t, sdkerrors.ErrEVMVMError.ABCICode(), res.Code) receipt, err = k.GetReceipt(ctx, common.HexToHash(res.EvmTxInfo.TxHash)) require.Nil(t, err) require.Equal(t, receipt.VmError, res.EvmTxInfo.VmError) diff --git a/x/evm/keeper/pointer.go b/x/evm/keeper/pointer.go index 33e1a6fed..8c80c64a8 100644 --- a/x/evm/keeper/pointer.go +++ b/x/evm/keeper/pointer.go @@ -2,7 +2,6 @@ package keeper import ( "encoding/binary" - "fmt" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -18,6 +17,9 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/types" ) +type PointerGetter func(sdk.Context, string) (common.Address, uint16, bool) +type PointerSetter func(sdk.Context, string, common.Address) error + var ErrorPointerToPointerNotAllowed = sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "cannot create a pointer to a pointer") // ERC20 -> Native Token @@ -219,10 +221,6 @@ func (k *Keeper) GetPointerInfo(ctx sdk.Context, pref []byte) (addr []byte, vers } func (k *Keeper) setPointerInfo(ctx sdk.Context, pref []byte, addr []byte, version uint16) error { - existingAddr, existingVersion, exists := k.GetPointerInfo(ctx, pref) - if exists && existingVersion >= version { - return fmt.Errorf("pointer at %X with version %d exists when trying to set pointer for version %d", string(existingAddr), existingVersion, version) - } store := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), pref) versionBz := make([]byte, 2) binary.BigEndian.PutUint16(versionBz, version) diff --git a/x/evm/keeper/pointer_test.go b/x/evm/keeper/pointer_test.go index 3563ebeeb..b34724c88 100644 --- a/x/evm/keeper/pointer_test.go +++ b/x/evm/keeper/pointer_test.go @@ -126,7 +126,6 @@ func TestEVMtoCWPointers(t *testing.T) { addr, _, exists := handlers.evmGetter(ctx, cwAddress.String()) require.Equal(t, evmAddress, addr) require.True(t, exists) - require.NotNil(t, handlers.evmSetter(ctx, cwAddress.String(), evmAddress)) // should delete var version uint16 = 1 @@ -231,7 +230,6 @@ func TestCWtoEVMPointers(t *testing.T) { addr, _, exists := handlers.cwGetter(ctx, evmAddress) require.Equal(t, cwAddress, addr) require.True(t, exists) - require.NotNil(t, handlers.cwSetter(ctx, evmAddress, cwAddress.String())) // create new address to test prevention logic cwAddress2, evmAddress2 := testkeeper.MockAddressPair() diff --git a/x/evm/keeper/pointer_upgrade.go b/x/evm/keeper/pointer_upgrade.go new file mode 100644 index 000000000..04d6aebcf --- /dev/null +++ b/x/evm/keeper/pointer_upgrade.go @@ -0,0 +1,107 @@ +package keeper + +import ( + "math" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/artifacts" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +func (k *Keeper) RunWithOneOffEVMInstance( + ctx sdk.Context, runner func(*vm.EVM) error, logger func(string, string), +) error { + stateDB := state.NewDBImpl(ctx, k, false) + evmModuleAddress := k.GetEVMAddressOrDefault(ctx, k.AccountKeeper().GetModuleAddress(types.ModuleName)) + gp := core.GasPool(math.MaxUint64) + blockCtx, err := k.GetVMBlockContext(ctx, gp) + if err != nil { + logger("get block context", err.Error()) + return err + } + cfg := types.DefaultChainConfig().EthereumConfig(k.ChainID(ctx)) + txCtx := core.NewEVMTxContext(&core.Message{From: evmModuleAddress, GasPrice: utils.Big0}) + evmInstance := vm.NewEVM(*blockCtx, txCtx, stateDB, cfg, vm.Config{}) + err = runner(evmInstance) + if err != nil { + logger("upserting pointer", err.Error()) + return err + } + surplus, err := stateDB.Finalize() + if err != nil { + logger("finalizing", err.Error()) + return err + } + if !surplus.IsZero() { + logger("non-zero surplus", surplus.String()) + } + return nil +} + +func (k *Keeper) UpsertERCNativePointer( + ctx sdk.Context, evm *vm.EVM, suppliedGas uint64, token string, metadata utils.ERCMetadata, +) (contractAddr common.Address, remainingGas uint64, err error) { + return k.UpsertERCPointer( + ctx, evm, suppliedGas, "native", []interface{}{ + token, metadata.Name, metadata.Symbol, metadata.Decimals, + }, k.GetERC20NativePointer, k.SetERC20NativePointer, + ) +} + +func (k *Keeper) UpsertERCCW20Pointer( + ctx sdk.Context, evm *vm.EVM, suppliedGas uint64, cw20Addr string, metadata utils.ERCMetadata, +) (contractAddr common.Address, remainingGas uint64, err error) { + return k.UpsertERCPointer( + ctx, evm, suppliedGas, "cw20", []interface{}{ + cw20Addr, metadata.Name, metadata.Symbol, + }, k.GetERC20CW20Pointer, k.SetERC20CW20Pointer, + ) +} + +func (k *Keeper) UpsertERCCW721Pointer( + ctx sdk.Context, evm *vm.EVM, suppliedGas uint64, cw721Addr string, metadata utils.ERCMetadata, +) (contractAddr common.Address, remainingGas uint64, err error) { + return k.UpsertERCPointer( + ctx, evm, suppliedGas, "cw721", []interface{}{ + cw721Addr, metadata.Name, metadata.Symbol, + }, k.GetERC721CW721Pointer, k.SetERC721CW721Pointer, + ) +} + +func (k *Keeper) UpsertERCPointer( + ctx sdk.Context, evm *vm.EVM, suppliedGas uint64, typ string, args []interface{}, getter PointerGetter, setter PointerSetter, +) (contractAddr common.Address, remainingGas uint64, err error) { + pointee := args[0].(string) + evmModuleAddress := k.GetEVMAddressOrDefault(ctx, k.AccountKeeper().GetModuleAddress(types.ModuleName)) + + var bin []byte + bin, err = artifacts.GetParsedABI(typ).Pack("", args...) + if err != nil { + panic(err) + } + bin = append(artifacts.GetBin(typ), bin...) + existingAddr, _, exists := getter(ctx, pointee) + if exists { + var ret []byte + contractAddr = existingAddr + ret, remainingGas, err = evm.GetDeploymentCode(vm.AccountRef(evmModuleAddress), bin, suppliedGas, utils.Big0, existingAddr) + k.SetCode(ctx, contractAddr, ret) + } else { + _, contractAddr, remainingGas, err = evm.Create(vm.AccountRef(evmModuleAddress), bin, suppliedGas, utils.Big0) + } + if err != nil { + return + } + if err = setter(ctx, pointee, contractAddr); err != nil { + return + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, typ), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, pointee))) + return +} diff --git a/x/evm/keeper/pointer_upgrade_test.go b/x/evm/keeper/pointer_upgrade_test.go new file mode 100644 index 000000000..cc4db4d93 --- /dev/null +++ b/x/evm/keeper/pointer_upgrade_test.go @@ -0,0 +1,115 @@ +package keeper_test + +import ( + "errors" + "math" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/utils" + "github.com/stretchr/testify/require" +) + +func TestRunWithOneOffEVMInstance(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + errLog := "" + errRunner := func(*vm.EVM) error { return errors.New("test") } + errLogger := func(a string, b string) { errLog = a + " " + b } + require.NotNil(t, k.RunWithOneOffEVMInstance(ctx, errRunner, errLogger)) + require.Equal(t, "upserting pointer test", errLog) + succLog := "" + succRunner := func(*vm.EVM) error { return nil } + succLogger := func(string, string) { succLog = "unexpected" } + require.Nil(t, k.RunWithOneOffEVMInstance(ctx, succRunner, succLogger)) + require.Empty(t, succLog) +} + +func TestUpsertERCNativePointer(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + var addr common.Address + err := k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + a, _, err := k.UpsertERCNativePointer(ctx, e, math.MaxUint64, "test", utils.ERCMetadata{ + Name: "test", + Symbol: "test", + Decimals: 6, + }) + addr = a + return err + }, func(s1, s2 string) {}) + require.Nil(t, err) + var newAddr common.Address + err = k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + a, _, err := k.UpsertERCNativePointer(ctx, e, math.MaxUint64, "test", utils.ERCMetadata{ + Name: "test2", + Symbol: "test2", + Decimals: 12, + }) + newAddr = a + return err + }, func(s1, s2 string) {}) + require.Nil(t, err) + require.Equal(t, addr, newAddr) + res, err := k.QueryERCSingleOutput(ctx, "native", addr, "name") + require.Nil(t, err) + require.Equal(t, "test2", res.(string)) + res, err = k.QueryERCSingleOutput(ctx, "native", addr, "symbol") + require.Nil(t, err) + require.Equal(t, "test2", res.(string)) + res, err = k.QueryERCSingleOutput(ctx, "native", addr, "decimals") + require.Nil(t, err) + require.Equal(t, uint8(12), res.(uint8)) + _, err = k.QueryERCSingleOutput(ctx, "native", addr, "nonexist") + require.NotNil(t, err) +} + +func TestUpsertERC20Pointer(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + var addr common.Address + err := k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + a, _, err := k.UpsertERCCW20Pointer(ctx, e, math.MaxUint64, "test", utils.ERCMetadata{ + Name: "test", + Symbol: "test", + }) + addr = a + return err + }, func(s1, s2 string) {}) + require.Nil(t, err) + var newAddr common.Address + err = k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + a, _, err := k.UpsertERCCW20Pointer(ctx, e, math.MaxUint64, "test", utils.ERCMetadata{ + Name: "test2", + Symbol: "test2", + }) + newAddr = a + return err + }, func(s1, s2 string) {}) + require.Nil(t, err) + require.Equal(t, addr, newAddr) +} + +func TestUpsertERC721Pointer(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + var addr common.Address + err := k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + a, _, err := k.UpsertERCCW721Pointer(ctx, e, math.MaxUint64, "test", utils.ERCMetadata{ + Name: "test", + Symbol: "test", + }) + addr = a + return err + }, func(s1, s2 string) {}) + require.Nil(t, err) + var newAddr common.Address + err = k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + a, _, err := k.UpsertERCCW721Pointer(ctx, e, math.MaxUint64, "test", utils.ERCMetadata{ + Name: "test2", + Symbol: "test2", + }) + newAddr = a + return err + }, func(s1, s2 string) {}) + require.Nil(t, err) + require.Equal(t, addr, newAddr) +} diff --git a/x/evm/keeper/receipt.go b/x/evm/keeper/receipt.go index 5872ea6a7..fa2c79e2b 100644 --- a/x/evm/keeper/receipt.go +++ b/x/evm/keeper/receipt.go @@ -9,6 +9,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/sei-protocol/sei-db/proto" + "github.com/ethereum/go-ethereum/core" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/sei-protocol/sei-chain/x/evm/types" ) @@ -23,6 +28,19 @@ func (k *Keeper) SetTransientReceipt(ctx sdk.Context, txHash common.Hash, receip return nil } +func (k *Keeper) GetTransientReceipt(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) { + store := ctx.TransientStore(k.transientStoreKey) + bz := store.Get(types.ReceiptKey(txHash)) + if bz == nil { + return nil, errors.New("not found") + } + r := &types.Receipt{} + if err := r.Unmarshal(bz); err != nil { + return nil, err + } + return r, nil +} + // GetReceipt returns a data structure that stores EVM specific transaction metadata. // Many EVM applications (e.g. MetaMask) relies on being on able to query receipt // by EVM transaction hash (not Sei transaction hash) to function properly. @@ -87,3 +105,56 @@ func (k *Keeper) FlushTransientReceipts(ctx sdk.Context) error { return k.receiptStore.ApplyChangesetAsync(ctx.BlockHeight(), changesets) } + +func (k *Keeper) WriteReceipt( + ctx sdk.Context, + stateDB *state.DBImpl, + msg *core.Message, + txType uint32, + txHash common.Hash, + gasUsed uint64, + vmError string, +) (*types.Receipt, error) { + ethLogs := stateDB.GetAllLogs() + bloom := ethtypes.CreateBloom(ethtypes.Receipts{ðtypes.Receipt{Logs: ethLogs}}) + receipt := &types.Receipt{ + TxType: txType, + CumulativeGasUsed: uint64(0), + TxHashHex: txHash.Hex(), + GasUsed: gasUsed, + BlockNumber: uint64(ctx.BlockHeight()), + TransactionIndex: uint32(ctx.TxIndex()), + EffectiveGasPrice: msg.GasPrice.Uint64(), + VmError: vmError, + Logs: utils.Map(ethLogs, ConvertEthLog), + LogsBloom: bloom[:], + } + + if msg.To == nil { + receipt.ContractAddress = crypto.CreateAddress(msg.From, msg.Nonce).Hex() + } else { + receipt.To = msg.To.Hex() + if len(msg.Data) > 0 { + receipt.ContractAddress = msg.To.Hex() + } + } + + if vmError == "" { + receipt.Status = uint32(ethtypes.ReceiptStatusSuccessful) + } else { + receipt.Status = uint32(ethtypes.ReceiptStatusFailed) + } + + if perr := stateDB.GetPrecompileError(); perr != nil { + if receipt.Status > 0 { + ctx.Logger().Error(fmt.Sprintf("Transaction %s succeeded in execution but has precompile error %s", receipt.TxHashHex, perr.Error())) + } else { + // append precompile error to VM error + receipt.VmError = fmt.Sprintf("%s|%s", receipt.VmError, perr.Error()) + } + } + + receipt.From = msg.From.Hex() + + return receipt, k.SetTransientReceipt(ctx, txHash, receipt) +} diff --git a/x/evm/keeper/view.go b/x/evm/keeper/view.go new file mode 100644 index 000000000..27a4d7fae --- /dev/null +++ b/x/evm/keeper/view.go @@ -0,0 +1,26 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/sei-protocol/sei-chain/x/evm/artifacts" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +func (k *Keeper) QueryERCSingleOutput(ctx sdk.Context, typ string, addr common.Address, query string) (interface{}, error) { + moduleAddr := k.AccountKeeper().GetModuleAddress(types.ModuleName) + q, _ := artifacts.GetParsedABI(typ).Pack(query) + r, err := k.StaticCallEVM(ctx, moduleAddr, &addr, q) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Error calling %s for %s due to %s, skipping", addr.Hex(), query, err)) + return nil, err + } + o, _ := artifacts.GetParsedABI(typ).Unpack(query, r) + if len(o) != 1 { + ctx.Logger().Error(fmt.Sprintf("Getting %d outputs when %s for %s, skipping", len(o), addr.Hex(), query)) + return nil, err + } + return o[0], nil +} diff --git a/x/evm/migrations/migrate_all_pointers.go b/x/evm/migrations/migrate_all_pointers.go new file mode 100644 index 000000000..31d650101 --- /dev/null +++ b/x/evm/migrations/migrate_all_pointers.go @@ -0,0 +1,177 @@ +package migrations + +import ( + "encoding/json" + "fmt" + "math" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/keeper" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +func MigrateERCNativePointers(ctx sdk.Context, k *keeper.Keeper) error { + iter := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), append(types.PointerRegistryPrefix, types.PointerERC20NativePrefix...)).ReverseIterator(nil, nil) + defer iter.Close() + seen := map[string]struct{}{} + for ; iter.Valid(); iter.Next() { + token := string(iter.Key()[:len(iter.Key())-2]) // last two bytes are version + if _, ok := seen[token]; ok { + continue + } + seen[token] = struct{}{} + addr := common.BytesToAddress(iter.Value()) + oName, err := k.QueryERCSingleOutput(ctx, "native", addr, "name") + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Failed to upgrade pointer for %s due to failed name query: %s", token, err)) + continue + } + oSymbol, err := k.QueryERCSingleOutput(ctx, "native", addr, "symbol") + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Failed to upgrade pointer for %s due to failed symbol query: %s", token, err)) + continue + } + oDecimals, err := k.QueryERCSingleOutput(ctx, "native", addr, "decimals") + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Failed to upgrade pointer for %s due to failed decimal query: %s", token, err)) + continue + } + _ = k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + _, _, err := k.UpsertERCNativePointer(ctx, e, math.MaxUint64, token, utils.ERCMetadata{ + Name: oName.(string), + Symbol: oSymbol.(string), + Decimals: oDecimals.(uint8), + }) + return err + }, func(s1, s2 string) { + ctx.Logger().Error(fmt.Sprintf("Failed to upgrade pointer for %s at step %s due to %s", token, s1, s2)) + }) + } + return nil +} + +func MigrateERCCW20Pointers(ctx sdk.Context, k *keeper.Keeper) error { + iter := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), append(types.PointerRegistryPrefix, types.PointerERC20CW20Prefix...)).ReverseIterator(nil, nil) + defer iter.Close() + seen := map[string]struct{}{} + for ; iter.Valid(); iter.Next() { + cwAddr := string(iter.Key()[:len(iter.Key())-2]) // last two bytes are version + if _, ok := seen[cwAddr]; ok { + continue + } + seen[cwAddr] = struct{}{} + addr := common.BytesToAddress(iter.Value()) + oName, err := k.QueryERCSingleOutput(ctx, "cw20", addr, "name") + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Failed to upgrade pointer for %s due to failed name query: %s", cwAddr, err)) + continue + } + oSymbol, err := k.QueryERCSingleOutput(ctx, "cw20", addr, "symbol") + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Failed to upgrade pointer for %s due to failed symbol query: %s", cwAddr, err)) + continue + } + _ = k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + _, _, err := k.UpsertERCCW20Pointer(ctx, e, math.MaxUint64, cwAddr, utils.ERCMetadata{ + Name: oName.(string), + Symbol: oSymbol.(string), + }) + return err + }, func(s1, s2 string) { + ctx.Logger().Error(fmt.Sprintf("Failed to upgrade pointer for %s at step %s due to %s", cwAddr, s1, s2)) + }) + } + return nil +} + +func MigrateERCCW721Pointers(ctx sdk.Context, k *keeper.Keeper) error { + iter := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), append(types.PointerRegistryPrefix, types.PointerERC721CW721Prefix...)).ReverseIterator(nil, nil) + defer iter.Close() + seen := map[string]struct{}{} + for ; iter.Valid(); iter.Next() { + cwAddr := string(iter.Key()[:len(iter.Key())-2]) // last two bytes are version + if _, ok := seen[cwAddr]; ok { + continue + } + seen[cwAddr] = struct{}{} + addr := common.BytesToAddress(iter.Value()) + oName, err := k.QueryERCSingleOutput(ctx, "cw721", addr, "name") + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Failed to upgrade pointer for %s due to failed name query: %s", cwAddr, err)) + continue + } + oSymbol, err := k.QueryERCSingleOutput(ctx, "cw721", addr, "symbol") + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Failed to upgrade pointer for %s due to failed symbol query: %s", cwAddr, err)) + continue + } + _ = k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + _, _, err := k.UpsertERCCW721Pointer(ctx, e, math.MaxUint64, cwAddr, utils.ERCMetadata{ + Name: oName.(string), + Symbol: oSymbol.(string), + }) + return err + }, func(s1, s2 string) { + ctx.Logger().Error(fmt.Sprintf("Failed to upgrade pointer for %s at step %s due to %s", cwAddr, s1, s2)) + }) + } + return nil +} + +func MigrateCWERC20Pointers(ctx sdk.Context, k *keeper.Keeper) error { + iter := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), append(types.PointerRegistryPrefix, types.PointerCW20ERC20Prefix...)).ReverseIterator(nil, nil) + defer iter.Close() + bz, _ := json.Marshal(map[string]interface{}{}) + moduleAcct := k.AccountKeeper().GetModuleAddress(types.ModuleName) + codeID := k.GetStoredPointerCodeID(ctx, types.PointerType_ERC20) + seen := map[string]struct{}{} + for ; iter.Valid(); iter.Next() { + evmAddr := string(iter.Key()[:len(iter.Key())-2]) // last two bytes are version + if _, ok := seen[evmAddr]; ok { + continue + } + seen[evmAddr] = struct{}{} + addr, err := sdk.AccAddressFromBech32(string(iter.Value())) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("error parsing cw-erc20 pointer %s address %s", string(iter.Value()), err)) + return err + } + _, err = k.WasmKeeper().Migrate(ctx, addr, moduleAcct, codeID, bz) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("error migrating cw-erc20 pointer %s to code ID %d due to %s", addr.String(), codeID, err)) + return err + } + } + return nil +} + +func MigrateCWERC721Pointers(ctx sdk.Context, k *keeper.Keeper) error { + iter := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), append(types.PointerRegistryPrefix, types.PointerCW721ERC721Prefix...)).ReverseIterator(nil, nil) + defer iter.Close() + bz, _ := json.Marshal(map[string]interface{}{}) + moduleAcct := k.AccountKeeper().GetModuleAddress(types.ModuleName) + codeID := k.GetStoredPointerCodeID(ctx, types.PointerType_ERC721) + seen := map[string]struct{}{} + for ; iter.Valid(); iter.Next() { + evmAddr := string(iter.Key()[:len(iter.Key())-2]) // last two bytes are version + if _, ok := seen[evmAddr]; ok { + continue + } + seen[evmAddr] = struct{}{} + addr, err := sdk.AccAddressFromBech32(string(iter.Value())) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("error parsing cw-erc20 pointer %s address %s", string(iter.Value()), err)) + return err + } + _, err = k.WasmKeeper().Migrate(ctx, addr, moduleAcct, codeID, bz) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("error migrating cw-erc721 pointer %s to code ID %d due to %s", addr.String(), codeID, err)) + return err + } + } + return nil +} diff --git a/x/evm/migrations/migrate_all_pointers_test.go b/x/evm/migrations/migrate_all_pointers_test.go new file mode 100644 index 000000000..a6f6420e8 --- /dev/null +++ b/x/evm/migrations/migrate_all_pointers_test.go @@ -0,0 +1,94 @@ +package migrations_test + +import ( + "math" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/keeper" + "github.com/sei-protocol/sei-chain/x/evm/migrations" + "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/stretchr/testify/require" +) + +func TestMigrateERCNativePointers(t *testing.T) { + k := testkeeper.EVMTestApp.EvmKeeper + ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()) + var pointerAddr common.Address + require.Nil(t, k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + a, _, err := k.UpsertERCNativePointer(ctx, e, math.MaxUint64, "test", utils.ERCMetadata{Name: "name", Symbol: "symbol", Decimals: 6}) + pointerAddr = a + return err + }, func(s1, s2 string) {})) + require.Nil(t, migrations.MigrateERCNativePointers(ctx, &k)) + // address should stay the same + addr, _, _ := k.GetERC20NativePointer(ctx, "test") + require.Equal(t, pointerAddr, addr) +} + +func TestMigrateERCCW20Pointers(t *testing.T) { + k := testkeeper.EVMTestApp.EvmKeeper + ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()) + var pointerAddr common.Address + require.Nil(t, k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + a, _, err := k.UpsertERCCW20Pointer(ctx, e, math.MaxUint64, "test", utils.ERCMetadata{Name: "name", Symbol: "symbol"}) + pointerAddr = a + return err + }, func(s1, s2 string) {})) + require.Nil(t, migrations.MigrateERCCW20Pointers(ctx, &k)) + // address should stay the same + addr, _, _ := k.GetERC20CW20Pointer(ctx, "test") + require.Equal(t, pointerAddr, addr) +} + +func TestMigrateERCCW721Pointers(t *testing.T) { + k := testkeeper.EVMTestApp.EvmKeeper + ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()) + var pointerAddr common.Address + require.Nil(t, k.RunWithOneOffEVMInstance(ctx, func(e *vm.EVM) error { + a, _, err := k.UpsertERCCW721Pointer(ctx, e, math.MaxUint64, "test", utils.ERCMetadata{Name: "name", Symbol: "symbol"}) + pointerAddr = a + return err + }, func(s1, s2 string) {})) + require.Nil(t, migrations.MigrateERCCW721Pointers(ctx, &k)) + // address should stay the same + addr, _, _ := k.GetERC721CW721Pointer(ctx, "test") + require.Equal(t, pointerAddr, addr) +} + +func TestMigrateCWERC20Pointers(t *testing.T) { + k := testkeeper.EVMTestApp.EvmKeeper + ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()) + require.Nil(t, migrations.StoreCWPointerCode(ctx, &k, true, false)) + msgServer := keeper.NewMsgServerImpl(&k) + res, err := msgServer.RegisterPointer(sdk.WrapSDKContext(ctx), &types.MsgRegisterPointer{ + PointerType: types.PointerType_ERC20, + ErcAddress: "0x0000000000000000000000000000000000000000", + }) + require.Nil(t, err) + require.Nil(t, migrations.MigrateCWERC20Pointers(ctx, &k)) + // address should stay the same + addr, _, _ := k.GetCW20ERC20Pointer(ctx, common.HexToAddress("0x0000000000000000000000000000000000000000")) + require.Equal(t, res.PointerAddress, addr.String()) +} + +func TestMigrateCWERC721Pointers(t *testing.T) { + k := testkeeper.EVMTestApp.EvmKeeper + ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()) + require.Nil(t, migrations.StoreCWPointerCode(ctx, &k, false, true)) + msgServer := keeper.NewMsgServerImpl(&k) + res, err := msgServer.RegisterPointer(sdk.WrapSDKContext(ctx), &types.MsgRegisterPointer{ + PointerType: types.PointerType_ERC721, + ErcAddress: "0x0000000000000000000000000000000000000000", + }) + require.Nil(t, err) + require.Nil(t, migrations.MigrateCWERC721Pointers(ctx, &k)) + // address should stay the same + addr, _, _ := k.GetCW721ERC721Pointer(ctx, common.HexToAddress("0x0000000000000000000000000000000000000000")) + require.Equal(t, res.PointerAddress, addr.String()) +} diff --git a/x/evm/migrations/migrate_cast_address_balances.go b/x/evm/migrations/migrate_cast_address_balances.go new file mode 100644 index 000000000..a620cb304 --- /dev/null +++ b/x/evm/migrations/migrate_cast_address_balances.go @@ -0,0 +1,31 @@ +package migrations + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/sei-protocol/sei-chain/x/evm/keeper" +) + +func MigrateCastAddressBalances(ctx sdk.Context, k *keeper.Keeper) (rerr error) { + k.IterateSeiAddressMapping(ctx, func(evmAddr common.Address, seiAddr sdk.AccAddress) bool { + castAddr := sdk.AccAddress(evmAddr[:]) + if balances := k.BankKeeper().SpendableCoins(ctx, castAddr); !balances.IsZero() { + if err := k.BankKeeper().SendCoins(ctx, castAddr, seiAddr, balances); err != nil { + ctx.Logger().Error(fmt.Sprintf("error migrating balances from cast address to real address for %s due to %s", evmAddr.Hex(), err)) + rerr = err + return true + } + } + if wei := k.BankKeeper().GetWeiBalance(ctx, castAddr); !wei.IsZero() { + if err := k.BankKeeper().SendCoinsAndWei(ctx, castAddr, seiAddr, sdk.ZeroInt(), wei); err != nil { + ctx.Logger().Error(fmt.Sprintf("error migrating wei from cast address to real address for %s due to %s", evmAddr.Hex(), err)) + rerr = err + return true + } + } + return false + }) + return +} diff --git a/x/evm/migrations/migrate_cast_address_balances_test.go b/x/evm/migrations/migrate_cast_address_balances_test.go new file mode 100644 index 000000000..e770637fa --- /dev/null +++ b/x/evm/migrations/migrate_cast_address_balances_test.go @@ -0,0 +1,37 @@ +package migrations_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/x/evm/migrations" + "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/stretchr/testify/require" +) + +func TestMigrateCastAddressBalances(t *testing.T) { + k := testkeeper.EVMTestApp.EvmKeeper + ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()) + require.Nil(t, k.BankKeeper().MintCoins(ctx, types.ModuleName, testkeeper.UseiCoins(100))) + // unassociated account with funds + seiAddr1, evmAddr1 := testkeeper.MockAddressPair() + require.Nil(t, k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, sdk.AccAddress(evmAddr1[:]), testkeeper.UseiCoins(10))) + // associated account without funds + seiAddr2, evmAddr2 := testkeeper.MockAddressPair() + k.SetAddressMapping(ctx, seiAddr2, evmAddr2) + // associated account with funds + seiAddr3, evmAddr3 := testkeeper.MockAddressPair() + require.Nil(t, k.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, sdk.AccAddress(evmAddr3[:]), testkeeper.UseiCoins(10))) + k.SetAddressMapping(ctx, seiAddr3, evmAddr3) + + require.Nil(t, migrations.MigrateCastAddressBalances(ctx, &k)) + + require.Equal(t, sdk.NewInt(10), k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr1[:]), "usei").Amount) + require.Equal(t, sdk.ZeroInt(), k.BankKeeper().GetBalance(ctx, seiAddr1, "usei").Amount) + require.Equal(t, sdk.ZeroInt(), k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr2[:]), "usei").Amount) + require.Equal(t, sdk.ZeroInt(), k.BankKeeper().GetBalance(ctx, seiAddr2, "usei").Amount) + require.Equal(t, sdk.ZeroInt(), k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr3[:]), "usei").Amount) + require.Equal(t, sdk.NewInt(10), k.BankKeeper().GetBalance(ctx, seiAddr3, "usei").Amount) +} diff --git a/x/evm/module.go b/x/evm/module.go index 1baa80c21..b01858a70 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -64,7 +64,7 @@ func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { return cdc.MustMarshalJSON(types.DefaultGenesis()) } -// ValidateGenesis performs genesis state validation for the capability module. +// ValidateGenesis performs genesis state validation for the evm module. func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { var genState types.GenesisState if err := cdc.UnmarshalJSON(bz, &genState); err != nil { @@ -73,6 +73,32 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingCo return genState.Validate() } +// ValidateGenesisStream performs genesis state validation for the evm module in a streaming fashion. +func (am AppModuleBasic) ValidateGenesisStream(cdc codec.JSONCodec, config client.TxEncodingConfig, genesisCh <-chan json.RawMessage) error { + genesisStateCh := make(chan types.GenesisState) + var err error + doneCh := make(chan struct{}) + go func() { + err = types.ValidateStream(genesisStateCh) + doneCh <- struct{}{} + }() + go func() { + defer close(genesisStateCh) + for genesis := range genesisCh { + var data types.GenesisState + err_ := cdc.UnmarshalJSON(genesis, &data) + if err_ != nil { + err = err_ + doneCh <- struct{}{} + return + } + genesisStateCh <- data + } + }() + <-doneCh + return err +} + // RegisterRESTRoutes registers the capability module's REST service handlers. func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {} @@ -159,6 +185,30 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { _ = cfg.RegisterMigration(types.ModuleName, 7, func(ctx sdk.Context) error { return migrations.StoreCWPointerCode(ctx, am.keeper, false, true) }) + + _ = cfg.RegisterMigration(types.ModuleName, 8, func(ctx sdk.Context) error { + if err := migrations.MigrateERCNativePointers(ctx, am.keeper); err != nil { + return err + } + if err := migrations.MigrateERCCW20Pointers(ctx, am.keeper); err != nil { + return err + } + return migrations.MigrateERCCW721Pointers(ctx, am.keeper) + }) + + _ = cfg.RegisterMigration(types.ModuleName, 9, func(ctx sdk.Context) error { + if err := migrations.StoreCWPointerCode(ctx, am.keeper, true, true); err != nil { + return err + } + if err := migrations.MigrateCWERC20Pointers(ctx, am.keeper); err != nil { + return err + } + return migrations.MigrateCWERC721Pointers(ctx, am.keeper) + }) + + _ = cfg.RegisterMigration(types.ModuleName, 10, func(ctx sdk.Context) error { + return migrations.MigrateCastAddressBalances(ctx, am.keeper) + }) } // RegisterInvariants registers the capability module's invariants. @@ -182,8 +232,21 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw return cdc.MustMarshalJSON(genState) } +// ExportGenesisStream returns the evm module's exported genesis state as raw JSON bytes in a streaming fashion. +func (am AppModule) ExportGenesisStream(ctx sdk.Context, cdc codec.JSONCodec) <-chan json.RawMessage { + ch := ExportGenesisStream(ctx, am.keeper) + chRaw := make(chan json.RawMessage) + go func() { + for genState := range ch { + chRaw <- cdc.MustMarshalJSON(genState) + } + close(chRaw) + }() + return chRaw +} + // ConsensusVersion implements ConsensusVersion. -func (AppModule) ConsensusVersion() uint64 { return 8 } +func (AppModule) ConsensusVersion() uint64 { return 11 } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { @@ -225,14 +288,13 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val } else { coinbase = am.keeper.AccountKeeper().GetModuleAddress(authtypes.FeeCollectorName) } - evmTxDeferredInfoList := am.keeper.GetEVMTxDeferredInfo(ctx) + evmTxDeferredInfoList := am.keeper.GetAllEVMTxDeferredInfo(ctx) evmHooks := tracers.GetCtxEthTracingHooks(ctx) var coinbaseEVMAddress common.Address if evmHooks != nil { coinbaseEVMAddress = am.keeper.GetEVMAddressOrDefault(ctx, coinbase) } - denom := am.keeper.GetBaseDenom(ctx) surplus := am.keeper.GetAnteSurplusSum(ctx) for _, deferredInfo := range evmTxDeferredInfoList { diff --git a/x/evm/module_test.go b/x/evm/module_test.go index 805e597be..cc59d3afc 100644 --- a/x/evm/module_test.go +++ b/x/evm/module_test.go @@ -57,13 +57,13 @@ func TestModuleExportGenesis(t *testing.T) { module := evm.NewAppModule(nil, k) jsonMsg := module.ExportGenesis(ctx, types.ModuleCdc) jsonStr := string(jsonMsg) - assert.Equal(t, "{\"params\":{\"priority_normalizer\":\"1.000000000000000000\",\"base_fee_per_gas\":\"0.000000000000000000\",\"minimum_fee_per_gas\":\"1000000000.000000000000000000\",\"whitelisted_cw_code_hashes_for_delegate_call\":[]},\"address_associations\":[{\"sei_address\":\"sei17xpfvakm2amg962yls6f84z3kell8c5la4jkdu\",\"eth_address\":\"0x27F7B8B8B5A4e71E8E9aA671f4e4031E3773303F\"}],\"codes\":[],\"states\":[],\"nonces\":[],\"serialized\":[{\"prefix\":\"Fg==\",\"key\":\"AwAB\",\"value\":\"AAAAAAAAAAM=\"},{\"prefix\":\"Fg==\",\"key\":\"BAAF\",\"value\":\"AAAAAAAAAAQ=\"}]}", jsonStr) + assert.Equal(t, "{\"params\":{\"priority_normalizer\":\"1.000000000000000000\",\"base_fee_per_gas\":\"0.000000000000000000\",\"minimum_fee_per_gas\":\"1000000000.000000000000000000\",\"whitelisted_cw_code_hashes_for_delegate_call\":[]},\"address_associations\":[{\"sei_address\":\"sei17xpfvakm2amg962yls6f84z3kell8c5la4jkdu\",\"eth_address\":\"0x27F7B8B8B5A4e71E8E9aA671f4e4031E3773303F\"}],\"codes\":[],\"states\":[],\"nonces\":[],\"serialized\":[{\"prefix\":\"Fg==\",\"key\":\"AwAC\",\"value\":\"AAAAAAAAAAM=\"},{\"prefix\":\"Fg==\",\"key\":\"BAAG\",\"value\":\"AAAAAAAAAAQ=\"}]}", jsonStr) } func TestConsensusVersion(t *testing.T) { k, _ := testkeeper.MockEVMKeeper() module := evm.NewAppModule(nil, k) - assert.Equal(t, uint64(8), module.ConsensusVersion()) + assert.Equal(t, uint64(11), module.ConsensusVersion()) } func TestABCI(t *testing.T) { diff --git a/x/evm/pointer_abi.json b/x/evm/pointer_abi.json new file mode 100644 index 000000000..8fb8238da --- /dev/null +++ b/x/evm/pointer_abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"cwAddr","type":"string"}],"name":"addCW20Pointer","outputs":[{"internalType":"address","name":"ret","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"cwAddr","type":"string"}],"name":"addCW721Pointer","outputs":[{"internalType":"address","name":"ret","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"token","type":"string"}],"name":"addNativePointer","outputs":[{"internalType":"address","name":"ret","type":"address"}],"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/x/evm/state/accesslist.go b/x/evm/state/accesslist.go index 21b98fd3a..f3c558e01 100644 --- a/x/evm/state/accesslist.go +++ b/x/evm/state/accesslist.go @@ -1,41 +1,61 @@ package state import ( - "encoding/json" - "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" ) +// all custom precompiles have an address greater than or equal to this address +var CustomPrecompileStartingAddr = common.HexToAddress("0x0000000000000000000000000000000000001001") + // Forked from go-ethereum, except journaling logic which is unnecessary with cacheKV type accessList struct { - Addresses map[common.Address]int `json:"addresses"` - Slots []map[common.Hash]struct{} `json:"slots"` + Addresses map[common.Address]int + Slots []map[common.Hash]struct{} } func (s *DBImpl) AddressInAccessList(addr common.Address) bool { s.k.PrepareReplayedAddr(s.ctx, addr) - _, ok := s.getAccessList().Addresses[addr] - return ok + _, ok := s.getCurrentAccessList().Addresses[addr] + if ok { + return true + } + for _, ts := range s.tempStatesHist { + if _, ok := ts.transientAccessLists.Addresses[addr]; ok { + return true + } + } + return false } func (s *DBImpl) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { s.k.PrepareReplayedAddr(s.ctx, addr) - al := s.getAccessList() - idx, ok := al.Addresses[addr] - if ok && idx != -1 { + al := s.getCurrentAccessList() + idx, addrOk := al.Addresses[addr] + if addrOk && idx != -1 { _, slotOk := al.Slots[idx][slot] - return ok, slotOk + if slotOk { + return true, true + } + } + for _, ts := range s.tempStatesHist { + idx, ok := ts.transientAccessLists.Addresses[addr] + addrOk = addrOk || ok + if ok && idx != -1 { + _, slotOk := ts.transientAccessLists.Slots[idx][slot] + if slotOk { + return true, true + } + } } - return ok, false + return addrOk, false } func (s *DBImpl) AddAddressToAccessList(addr common.Address) { s.k.PrepareReplayedAddr(s.ctx, addr) - al := s.getAccessList() - defer s.saveAccessList(al) + al := s.getCurrentAccessList() if _, present := al.Addresses[addr]; present { return } @@ -44,8 +64,7 @@ func (s *DBImpl) AddAddressToAccessList(addr common.Address) { func (s *DBImpl) AddSlotToAccessList(addr common.Address, slot common.Hash) { s.k.PrepareReplayedAddr(s.ctx, addr) - al := s.getAccessList() - defer s.saveAccessList(al) + al := s.getCurrentAccessList() idx, addrPresent := al.Addresses[addr] if !addrPresent || idx == -1 { // Address not present, or addr present but no slots there @@ -74,6 +93,10 @@ func (s *DBImpl) Prepare(_ params.Rules, sender, coinbase common.Address, dest * // If it's a create-tx, the destination will be added inside evm.create } for _, addr := range precompiles { + // skip any custom precompile + if addr.Cmp(CustomPrecompileStartingAddr) >= 0 { + continue + } s.AddAddressToAccessList(addr) } for _, el := range txAccesses { @@ -85,22 +108,6 @@ func (s *DBImpl) Prepare(_ params.Rules, sender, coinbase common.Address, dest * s.AddAddressToAccessList(coinbase) } -func (s *DBImpl) getAccessList() *accessList { - bz, found := s.getTransientModule(AccessListKey) - al := accessList{Addresses: make(map[common.Address]int)} - if !found || bz == nil { - return &al - } - if err := json.Unmarshal(bz, &al); err != nil { - panic(err) - } - return &al -} - -func (s *DBImpl) saveAccessList(al *accessList) { - albz, err := json.Marshal(al) - if err != nil { - panic(err) - } - s.tempStateCurrent.transientModuleStates[string(AccessListKey)] = albz +func (s *DBImpl) getCurrentAccessList() *accessList { + return s.tempStateCurrent.transientAccessLists } diff --git a/x/evm/state/balance.go b/x/evm/state/balance.go index dc2b9857f..a0a761cd6 100644 --- a/x/evm/state/balance.go +++ b/x/evm/state/balance.go @@ -92,12 +92,7 @@ func (s *DBImpl) AddBalance(evmAddr common.Address, amt *big.Int, reason tracing func (s *DBImpl) GetBalance(evmAddr common.Address) *big.Int { s.k.PrepareReplayedAddr(s.ctx, evmAddr) seiAddr := s.getSeiAddress(evmAddr) - denom := s.k.GetBaseDenom(s.ctx) - allUsei := s.k.BankKeeper().GetBalance(s.ctx, seiAddr, denom).Amount - lockedUsei := s.k.BankKeeper().LockedCoins(s.ctx, seiAddr).AmountOf(denom) // LockedCoins doesn't use iterators - usei := allUsei.Sub(lockedUsei) - wei := s.k.BankKeeper().GetWeiBalance(s.ctx, seiAddr) - return usei.Mul(SdkUseiToSweiMultiplier).Add(wei).BigInt() + return s.k.GetBalance(s.ctx, seiAddr) } // should only be called during simulation diff --git a/x/evm/state/check.go b/x/evm/state/check.go index 3b3a6b606..d0df9faa9 100644 --- a/x/evm/state/check.go +++ b/x/evm/state/check.go @@ -1,10 +1,7 @@ package state import ( - "bytes" - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/sei-protocol/sei-chain/utils" ) @@ -14,7 +11,7 @@ func (s *DBImpl) Exist(addr common.Address) bool { s.k.PrepareReplayedAddr(s.ctx, addr) // check if the address exists as a contract codeHash := s.GetCodeHash(addr) - if codeHash.Cmp(ethtypes.EmptyCodeHash) != 0 && s.GetCodeHash(addr).Cmp(common.Hash{}) != 0 { + if codeHash.Cmp(common.Hash{}) != 0 { return true } @@ -36,5 +33,5 @@ func (s *DBImpl) Exist(addr common.Address) bool { // is defined according to EIP161 (balance = nonce = code = 0). func (s *DBImpl) Empty(addr common.Address) bool { s.k.PrepareReplayedAddr(s.ctx, addr) - return s.GetBalance(addr).Cmp(utils.Big0) == 0 && s.GetNonce(addr) == 0 && bytes.Equal(s.GetCodeHash(addr).Bytes(), ethtypes.EmptyCodeHash.Bytes()) + return s.GetBalance(addr).Cmp(utils.Big0) == 0 && s.GetNonce(addr) == 0 && s.GetCodeHash(addr).Cmp(common.Hash{}) == 0 } diff --git a/x/evm/state/code_test.go b/x/evm/state/code_test.go index 893077045..e6491a0b2 100644 --- a/x/evm/state/code_test.go +++ b/x/evm/state/code_test.go @@ -3,7 +3,7 @@ package state_test import ( "testing" - ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/state" @@ -15,7 +15,7 @@ func TestCode(t *testing.T) { _, addr := testkeeper.MockAddressPair() statedb := state.NewDBImpl(ctx, k, false) - require.Equal(t, ethtypes.EmptyCodeHash, statedb.GetCodeHash(addr)) + require.Equal(t, common.Hash{}, statedb.GetCodeHash(addr)) require.Nil(t, statedb.GetCode(addr)) require.Equal(t, 0, statedb.GetCodeSize(addr)) diff --git a/x/evm/state/expected_keepers.go b/x/evm/state/expected_keepers.go index b84137e8f..95f0e6e08 100644 --- a/x/evm/state/expected_keepers.go +++ b/x/evm/state/expected_keepers.go @@ -1,6 +1,8 @@ package state import ( + "math/big" + sdk "github.com/cosmos/cosmos-sdk/types" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" @@ -26,4 +28,5 @@ type EVMKeeper interface { GetNonce(sdk.Context, common.Address) uint64 SetNonce(sdk.Context, common.Address, uint64) PrepareReplayedAddr(ctx sdk.Context, addr common.Address) + GetBalance(ctx sdk.Context, addr sdk.AccAddress) *big.Int } diff --git a/x/evm/state/keys.go b/x/evm/state/keys.go index f99ec0769..6404c1366 100644 --- a/x/evm/state/keys.go +++ b/x/evm/state/keys.go @@ -9,9 +9,8 @@ var ( // If evm module balance is higher than this value at the end of // the transaction, we need to burn from module balance in order // for this number to align. - GasRefundKey = []byte{0x01} - LogsKey = []byte{0x02} - AccessListKey = []byte{0x03} + GasRefundKey = []byte{0x01} + LogsKey = []byte{0x02} ) /* diff --git a/x/evm/state/statedb.go b/x/evm/state/statedb.go index 7cd888e82..101735618 100644 --- a/x/evm/state/statedb.go +++ b/x/evm/state/statedb.go @@ -81,6 +81,13 @@ func (s *DBImpl) SetEVM(evm *vm.EVM) {} // to the database. func (s *DBImpl) AddPreimage(_ common.Hash, _ []byte) {} +func (s *DBImpl) Cleanup() { + s.tempStateCurrent = nil + s.tempStatesHist = []*TemporaryState{} + s.logger = nil + s.snapshottedCtxs = nil +} + func (s *DBImpl) Finalize() (surplus sdk.Int, err error) { if s.simulation { panic("should never call finalize on a simulation DB") @@ -199,6 +206,7 @@ type TemporaryState struct { transientStates map[string]map[string]common.Hash transientAccounts map[string][]byte transientModuleStates map[string][]byte + transientAccessLists *accessList surplus sdk.Int // in wei } @@ -208,6 +216,7 @@ func NewTemporaryState() *TemporaryState { transientStates: make(map[string]map[string]common.Hash), transientAccounts: make(map[string][]byte), transientModuleStates: make(map[string][]byte), + transientAccessLists: &accessList{Addresses: make(map[common.Address]int), Slots: []map[common.Hash]struct{}{}}, surplus: utils.Sdk0, } } diff --git a/x/evm/tracers/firehose.go b/x/evm/tracers/firehose.go index c83804a7b..9855429f7 100644 --- a/x/evm/tracers/firehose.go +++ b/x/evm/tracers/firehose.go @@ -22,6 +22,8 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/tracing" @@ -34,6 +36,7 @@ import ( "github.com/holiman/uint256" pbeth "github.com/sei-protocol/sei-chain/pb/sf/ethereum/type/v2" seitracing "github.com/sei-protocol/sei-chain/x/evm/tracing" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" "golang.org/x/exp/maps" "golang.org/x/exp/slices" "google.golang.org/protobuf/proto" @@ -127,6 +130,8 @@ func newSeiFirehoseTracer(tracerURL *url.URL) (*seitracing.Hooks, error) { OnSeiSystemCallStart: tracer.OnSystemCallStart, OnSeiSystemCallEnd: tracer.OnSystemCallEnd, + OnSeiPostTxCosmosEvents: tracer.OnSeiPostTxCosmosEvents, + GetTxTracer: func(txIndex int) sdk.TxTracer { // Created first so we can get the pointer id everywhere isolatedTracer := &TxTracerHooks{} @@ -139,6 +144,8 @@ func newSeiFirehoseTracer(tracerURL *url.URL) (*seitracing.Hooks, error) { OnSeiSystemCallStart: isolatedTxTracer.OnSystemCallStart, OnSeiSystemCallEnd: isolatedTxTracer.OnSystemCallEnd, + + OnSeiPostTxCosmosEvents: isolatedTxTracer.OnSeiPostTxCosmosEvents, } isolatedTracer.OnTxReset = func() { @@ -147,7 +154,7 @@ func newSeiFirehoseTracer(tracerURL *url.URL) (*seitracing.Hooks, error) { } isolatedTracer.OnTxCommit = func() { - firehoseInfo("committing isolated transaction tracer (tracer=%p, transient{transaction=%t, system_calls=%d})", isolatedTxTracer, isolatedTxTracer.transientTransaction != nil, len(isolatedTxTracer.transientSystemCalls)) + firehoseInfo("committing isolated transaction tracer (tracer=%s, transient{transaction=%t, system_calls=%d})", isolatedTracerID, isolatedTxTracer.transientTransaction != nil, len(isolatedTxTracer.transientSystemCalls)) // The `TxCommit` callback can be called for WASM and EVM transactions, the `transactionTransient` in the // case of a WASM execution, no EVM transaction will ever be started, so if `transactionTransient` is nil, @@ -212,6 +219,7 @@ type Firehose struct { // Block state block *pbeth.Block + blockHash common.Hash blockBaseFee *big.Int blockOrdinal *Ordinal blockFinality *FinalityStatus @@ -294,6 +302,7 @@ func (f *Firehose) newIsolatedTransactionTracer(traceId string) *Firehose { // resetBlock resets the block state only, do not reset transaction or call state func (f *Firehose) resetBlock() { f.block = nil + f.blockHash = emptyCommonHash f.blockBaseFee = nil f.blockOrdinal.Reset() f.blockFinality.Reset() @@ -329,7 +338,7 @@ func (f *Firehose) OnBlockchainInit(chainConfig *params.ChainConfig) { f.chainConfig = chainConfig if wasNeverSent := f.initSent.CompareAndSwap(false, true); wasNeverSent { - printToFirehose("INIT", FirehoseProtocolVersion, "geth", params.Version) + printToFirehose("INIT", FirehoseProtocolVersion, "sei-evm", "geth-"+params.Version) } else { f.panicInvalidState("The OnBlockchainInit callback was called more than once", 0) } @@ -364,6 +373,7 @@ func (f *Firehose) OnSeiBlockStart(hash []byte, size uint64, b *types.Header) { Size: size, Ver: 4, } + f.blockHash.SetBytes(hash) if f.block.Header.BaseFeePerGas != nil { f.blockBaseFee = f.block.Header.BaseFeePerGas.Native() @@ -381,8 +391,9 @@ func (f *Firehose) OnBlockStart(event tracing.BlockEvent) { f.blockRules = f.chainConfig.Rules(b.Number(), f.chainConfig.TerminalTotalDifficultyPassed, b.Time()) f.blockIsPrecompiledAddr = getActivePrecompilesChecker(f.blockRules) + f.blockHash = b.Hash() f.block = &pbeth.Block{ - Hash: b.Hash().Bytes(), + Hash: f.blockHash.Bytes(), Number: b.Number().Uint64(), Header: newBlockHeaderFromChainHeader(b.Header(), firehoseBigIntFromNative(new(big.Int).Add(event.TD, b.Difficulty()))), Size: b.Size(), @@ -439,7 +450,8 @@ func getActivePrecompilesChecker(rules params.Rules) func(addr common.Address) b } func (f *Firehose) OnBlockEnd(err error) { - firehoseInfo("block ending (err=%s)", errorView(err)) + blockNumber := f.block.Number + firehoseInfo("block ending (number=%d, trx=%d, err=%s)", blockNumber, len(f.block.TransactionTraces), errorView(err)) if err == nil { f.ensureInBlockAndNotInTrx() @@ -452,7 +464,7 @@ func (f *Firehose) OnBlockEnd(err error) { f.resetBlock() f.resetTransaction() - firehoseInfo("block end") + firehoseInfo("block end (number=%d)", blockNumber) } func (f *Firehose) addIsolatedTransaction(isolatedTrace *pbeth.TransactionTrace) { @@ -760,6 +772,221 @@ func (f *Firehose) assignOrdinalAndIndexToReceiptLogs() { } } +func (f *Firehose) OnSeiPostTxCosmosEvents(event seitracing.SeiPostTxCosmosEvent) { + if !event.OnEVMTransaction { + f.onPostTxCosmosEventsCoWasmTx(event) + return + } + + firehoseInfo("post tx cosmos events on EVM transaction (tracer=%s, added_logs=%d, isolated=%t)", f.tracerID, len(event.AddedLogs), f.transactionIsolated) + + transaction := f.transaction + if f.transactionIsolated { + transaction = f.transientTransaction + } + + if transaction == nil { + f.panicInvalidState("transaction (or transient transaction) must be set at this point", 1) + } + + if len(transaction.Calls) == 0 { + f.panicInvalidState("transaction must have at least one call at this point", 1) + } + + // Ok, we are adding new logs to the transaction, as such, we must update the `EndOrdinal` of the transaction. + // Indeed, when the method we are in is called, the transaction is actually already ended, so the `EndOrdinal` of + // the transaction is already "closed". + // + // We are kind of re-opening the transaction here and adding new ordinals that will be > than the transaction + // 'EndOrdinal'. The solution to this is to "rewind" the block ordinal by one, just like if `OnTxEnd` was not called. + // + // While adding the logs, we create Firehose logs and use `f.blockOrdinal.Next()` to assign the ordinal to the logs. Those + // will be set just like if the transaction was still open. + // + // At the end of this method, we will set again the `EndOrdinal` of the transaction to the next ordinal available + // right now, effectively closing the transaction again with the correct new final ordinal. + // + // Integration test `FirehoseTracerTest.js#CW20 transfer performed through ERC20 pointer contract` is testing this + // and verifying that the ordinals are correctly assigned. + f.blockOrdinal.Set(f.blockOrdinal.Peek() - 1) + + rootCall := transaction.Calls[0] + + for _, addedLog := range event.AddedLogs { + firehoseDebug("adding post log to tx (tracer=%s, address=%s [receipt has already %d logs])", f.tracerID, addedLog.Address, len(transaction.Receipt.Logs)) + firehoseLog := f.newFirehoseLogFromCosmos(addedLog) + + if rootCall != nil { + rootCall.Logs = append(rootCall.Logs, firehoseLog) + } + transaction.Receipt.Logs = append(transaction.Receipt.Logs, firehoseLog) + + f.transactionLogIndex += 1 + } + + transaction.Receipt.LogsBloom = event.NewReceipt.LogsBloom + transaction.EndOrdinal = f.blockOrdinal.Next() +} + +func (f *Firehose) onPostTxCosmosEventsCoWasmTx(event seitracing.SeiPostTxCosmosEvent) { + firehoseInfo("post tx cosmos events on CoWasm (non-EVM) transaction (tracer=%s, added_logs=%d)", f.tracerID, len(event.AddedLogs)) + + firehoseInfo("trx start (tracer=%s hash=%s isolated=%t", f.tracerID, event.TxHash, f.transactionIsolated) + f.ensureInBlockAndNotInTrxAndNotInCall() + + if len(event.NewReceipt.Logs) == 0 { + f.panicInvalidState(fmt.Sprintf("no logs added to the transaction %s via trace %s", event.TxHash.Bytes(), f.tracerID), 1) + } + + from := common.Address{} + to := common.HexToAddress(event.NewReceipt.Logs[0].Address) + + if event.NewReceipt.From != "" { + // The AddCosmosEventsToEVMReceiptIfApplicable sets the `From` field to the `NewReceipt.From` field when possible + // if sets, the `From` will be the EVM string address in hex. + from = common.HexToAddress(event.NewReceipt.From) + } + + f.transaction = &pbeth.TransactionTrace{ + BeginOrdinal: f.blockOrdinal.Next(), + Hash: event.TxHash[:], + From: from.Bytes(), + To: to.Bytes(), + } + + _ = extractCoWasmTxFirstSignature + // We haven't got the time to get this validated by Sei team if it was to correct way to extract + // R, S and V from the CoWasm transaction, so we are commenting it out for now. The R and S seems + // correct but the way to extract V is not clear, current logic in 'extractCoWasmTxFirstSignature' + // seems broken. + // if sigTx, ok := event.Tx.(authsigning.SigVerifiableTx); ok && len(sigTx.GetSigners()) > 0 { + // if r, s, v, data, err := extractCoWasmTxFirstSignature(sigTx); err != nil { + // firehoseInfo("signature from CoWasm transaction (error=%s)", err) + // } else { + // firehoseInfo("signature from CoWasm transaction (r=%s, s=%s, v=%d, cowasm{signature=%s, sign_mode=%d))", byteView(r), byteView(s), byteView(v), byteView(data.Signature), data.SignMode) + // f.transaction.R = normalizeSignaturePoint(r) + // f.transaction.S = normalizeSignaturePoint(s) + // f.transaction.V = emptyBytesToNil(v) + // } + // } + + f.OnCallEnter(0, 0, from, to, f.transaction.Input, f.transaction.GasLimit, nil) + for _, addedLog := range event.AddedLogs { + f.onLog(f.newFirehoseLogFromCosmos(addedLog)) + } + f.OnCallExit(0, nil, 0, nil, false) + + receipt := event.NewReceipt + receiptLogs := make([]*types.Log, len(event.AddedLogs)) + for i, addedLog := range event.AddedLogs { + receiptLogs[i] = f.newEthLogFromCosmos(addedLog, receipt) + } + f.OnTxEnd(&types.Receipt{ + // We cannot translate Sei transaction type encoded in a uint32 and set to `math.Uint32` to a uint8. So + // we simply set it to the maximum value of a uint8 and we will update the transaction type in the Firehose + // transaction trace Protobuf message so that 255 is assigned to Sei "SHELL_TYPE" and cross fingers that + // it's not in conflict already with another transaction type. + Type: math.MaxUint8, + Status: types.ReceiptStatusSuccessful, + CumulativeGasUsed: 0, + Bloom: types.Bloom(receipt.LogsBloom), + Logs: receiptLogs, + TxHash: event.TxHash, + ContractAddress: common.Address{}, + GasUsed: 0, + EffectiveGasPrice: nil, + + BlockHash: f.blockHash, + BlockNumber: big.NewInt(int64(receipt.BlockNumber)), + TransactionIndex: uint(receipt.TransactionIndex), + }, nil) +} + +func extractCoWasmTxFirstSignature(tx authsigning.SigVerifiableTx) (r, s []byte, v []byte, data *signing.SingleSignatureData, err error) { + signatures, err := tx.GetSignaturesV2() + if err != nil { + return nil, nil, nil, nil, err + } + + for _, signature := range signatures { + if single, ok := signature.Data.(*signing.SingleSignatureData); ok { + data = single + break + } + + if multi, ok := signature.Data.(*signing.MultiSignatureData); ok && len(multi.Signatures) > 0 { + if first, ok := multi.Signatures[0].(*signing.SingleSignatureData); ok { + data = first + break + } + } + } + + if data == nil { + return nil, nil, nil, nil, errors.New("no signature found") + } + + r = data.Signature[0:32] + s = data.Signature[32:64] + v = []byte{byte(data.SignMode - 27)} + return +} + +func (f *Firehose) newFirehoseLogFromCosmos(log *evmtypes.Log) *pbeth.Log { + address := common.HexToAddress(log.Address) + var topics [][]byte + if len(log.Topics) > 0 { + topics = make([][]byte, len(log.Topics)) + for i, topic := range log.Topics { + topics[i] = common.FromHex(topic) + } + } + + return &pbeth.Log{ + Address: address[:], + Topics: topics, + Data: log.Data, + + // In the Sei case, there the log.Index set is actually the transaction log's index, not the block log's index. + // This is because transactions are actually executed in parallel, so computing the block index would be possible + // only when the transaction is committed to the block or on block end. + // + // For now, we will use the same value for both as it fits the JSON-RPC of Sei. + Index: log.Index, + BlockIndex: log.Index, + + Ordinal: f.blockOrdinal.Next(), + } +} + +func (f *Firehose) newEthLogFromCosmos(log *evmtypes.Log, receipt *evmtypes.Receipt) *types.Log { + address := common.HexToAddress(log.Address) + var topics []common.Hash + if len(log.Topics) > 0 { + topics = make([]common.Hash, len(log.Topics)) + for i, topic := range log.Topics { + topics[i] = common.HexToHash(topic) + } + } + + return &types.Log{ + Address: address, + Topics: topics, + Data: log.Data, + + // This is actually the block index, but in Sei the block index is actual the transaction index + Index: uint(log.Index), + TxIndex: uint(receipt.TransactionIndex), + + BlockNumber: receipt.BlockNumber, + TxHash: common.HexToHash(receipt.TxHashHex), + + // We cannot compute the block hash, but it's fine as we do not use in Firehose tracer + BlockHash: common.Hash{}, + Removed: false, + } +} + // OnCallEnter implements the EVMLogger interface to initialize the tracing operation. func (f *Firehose) OnCallEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { opCode := vm.OpCode(typ) @@ -1242,10 +1469,7 @@ func (f *Firehose) OnLog(l *types.Log) { topics[i] = topic.Bytes() } - activeCall := f.callStack.Peek() - firehoseTrace("adding log to call (address=%s call=%d [has already %d logs])", l.Address, activeCall.Index, len(activeCall.Logs)) - - activeCall.Logs = append(activeCall.Logs, &pbeth.Log{ + f.onLog(&pbeth.Log{ Address: l.Address.Bytes(), Topics: topics, Data: l.Data, @@ -1253,6 +1477,13 @@ func (f *Firehose) OnLog(l *types.Log) { BlockIndex: uint32(l.Index), Ordinal: f.blockOrdinal.Next(), }) +} + +func (f *Firehose) onLog(l *pbeth.Log) { + activeCall := f.callStack.Peek() + firehoseDebug("adding log to call (address=%s call=%d topics=%d [has already %d logs])", byteView(l.Address), activeCall.Index, len(l.Topics), len(activeCall.Logs)) + + activeCall.Logs = append(activeCall.Logs, l) f.transactionLogIndex++ } @@ -1431,11 +1662,6 @@ func (f *Firehose) panicInvalidState(msg string, callerSkip int) string { // // It flushes this through [flushToFirehose] to the `os.Stdout` writer. func (f *Firehose) printBlockToFirehose(block *pbeth.Block, finalityStatus *FinalityStatus) { - marshalled, err := proto.Marshal(block) - if err != nil { - panic(fatal("failed to marshal block: %w", err)) - } - f.outputBuffer.Reset() previousHash := block.PreviousID() @@ -1458,13 +1684,18 @@ func (f *Firehose) printBlockToFirehose(block *pbeth.Block, finalityStatus *Fina // **Important* The final space in the Sprintf template is mandatory! f.outputBuffer.WriteString(fmt.Sprintf("FIRE BLOCK %d %s %d %s %d %d ", block.Number, hex.EncodeToString(block.Hash), previousNum, previousHash, libNum, block.Time().UnixNano())) + marshalled, err := proto.Marshal(block) + if err != nil { + panic(fatal("failed to marshal block: %w", err)) + } + encoder := base64.NewEncoder(base64.StdEncoding, f.outputBuffer) if _, err = encoder.Write(marshalled); err != nil { - panic(fatal("write to encoder should have been infaillible: %w", err)) + panic(fatal("write to encoder should have been infallible: %w", err)) } if err := encoder.Close(); err != nil { - panic(fatal("closing encoder should have been infaillible: %w", err)) + panic(fatal("closing encoder should have been infallible: %w", err)) } f.outputBuffer.WriteString("\n") @@ -1866,7 +2097,7 @@ func (o *Ordinal) Peek() (out uint64) { } // Next gives you the next sequential ordinal value that you should -// use to assign to your exeuction trace (block, transaction, call, etc). +// use to assign to your execution trace (block, transaction, call, etc). func (o *Ordinal) Next() (out uint64) { o.value++ diff --git a/x/evm/tracing/hooks.go b/x/evm/tracing/hooks.go index 351fd1dd2..dd50fcaa1 100644 --- a/x/evm/tracing/hooks.go +++ b/x/evm/tracing/hooks.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" ) // BlockEvent is emitted upon tracing an incoming block. @@ -27,25 +28,36 @@ type ( // convey how chain is progressing. E.g. known blocks will be skipped // when node is started after a crash. OnSeiBlockStartHook = func(hash []byte, size uint64, b *types.Header) - // OnSeiBlockEnd is called after executing `block` and receives the error + // OnSeiBlockEndHook is called after executing `block` and receives the error // that occurred during processing. If an `err` is received in the callback, // it means the block should be discarded (optimistic execution failed for example). - OnSeiBlockEnd = func(err error) + OnSeiBlockEndHook = func(err error) // OnSeiSystemCallStart is called before executing a system call in Sei context. CoWasm // contract execution that invokes the EVM from one ore another are examples of system calls. // // You can use this hook to route upcoming `OnEnter/OnExit` EVM tracing hook to be appended // to a system call bucket for the purpose of tracing the system calls. - OnSeiSystemCallStart = func(err error) + OnSeiSystemCallStartHook = func() // OnSeiSystemCallStart is called after executing a system call in Sei context. CoWasm // contract execution that invokes the EVM from one ore another are examples of system calls. // // You can use this hook to terminate special handling of `OnEnter/OnExit`. - OnSeiSystemCallEnd = func(err error) + OnSeiSystemCallEndHook = func() + + OnSeiPostTxCosmosEventsHook = func(event SeiPostTxCosmosEvent) ) +type SeiPostTxCosmosEvent struct { + Tx sdk.Tx + TxHash common.Hash + AddedLogs []*evmtypes.Log + NewReceipt *evmtypes.Receipt + OnEVMTransaction bool + EVMAddressOrDefault func(address sdk.AccAddress) common.Address +} + // Hooks is used to collect traces during chain processing. It's a similar // interface as the go-ethereum's [tracing.Hooks] but adapted to Sei particularities. // @@ -56,11 +68,13 @@ type Hooks struct { OnSeiBlockchainInit OnSeiBlockchainInitHook OnSeiBlockStart OnSeiBlockStartHook - OnSeiBlockEnd OnSeiBlockEnd + OnSeiBlockEnd OnSeiBlockEndHook - OnSeiSystemCallStart func() + OnSeiSystemCallStart OnSeiSystemCallStartHook OnSeiSystemCallEnd func() + OnSeiPostTxCosmosEvents OnSeiPostTxCosmosEventsHook + GetTxTracer func(txIndex int) sdk.TxTracer } diff --git a/x/evm/types/genesis.go b/x/evm/types/genesis.go index 22bf94966..9b5228c98 100644 --- a/x/evm/types/genesis.go +++ b/x/evm/types/genesis.go @@ -9,3 +9,19 @@ func DefaultGenesis() *GenesisState { func (gs GenesisState) Validate() error { return gs.Params.Validate() } + +func ValidateStream(gensisStateCh <-chan GenesisState) error { + passedParamCheck := false + var paramCheckErr error + for genesisState := range gensisStateCh { + if err := genesisState.Validate(); err != nil { + paramCheckErr = err + } else { + passedParamCheck = true + } + } + if !passedParamCheck { + return paramCheckErr + } + return nil +} diff --git a/x/evm/types/receipt.pb.go b/x/evm/types/receipt.pb.go index 1cbb846e8..b636d167b 100644 --- a/x/evm/types/receipt.pb.go +++ b/x/evm/types/receipt.pb.go @@ -24,10 +24,11 @@ var _ = math.Inf const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type Log struct { - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - Topics []string `protobuf:"bytes,2,rep,name=topics,proto3" json:"topics,omitempty"` - Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` - Index uint32 `protobuf:"varint,4,opt,name=index,proto3" json:"index,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Topics []string `protobuf:"bytes,2,rep,name=topics,proto3" json:"topics,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + Index uint32 `protobuf:"varint,4,opt,name=index,proto3" json:"index,omitempty"` + Synthetic bool `protobuf:"varint,5,opt,name=synthetic,proto3" json:"synthetic,omitempty"` } func (m *Log) Reset() { *m = Log{} } @@ -91,6 +92,13 @@ func (m *Log) GetIndex() uint32 { return 0 } +func (m *Log) GetSynthetic() bool { + if m != nil { + return m.Synthetic + } + return false +} + type Receipt struct { TxType uint32 `protobuf:"varint,1,opt,name=tx_type,json=txType,proto3" json:"tx_type,omitempty" yaml:"tx_type"` CumulativeGasUsed uint64 `protobuf:"varint,2,opt,name=cumulative_gas_used,json=cumulativeGasUsed,proto3" json:"cumulative_gas_used,omitempty" yaml:"cumulative_gas_used"` @@ -247,46 +255,47 @@ func init() { func init() { proto.RegisterFile("evm/receipt.proto", fileDescriptor_d864f6bdca684f52) } var fileDescriptor_d864f6bdca684f52 = []byte{ - // 610 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x53, 0x41, 0x4f, 0xdb, 0x4c, - 0x10, 0xc5, 0x49, 0x48, 0xf0, 0x86, 0x00, 0xd9, 0x20, 0x58, 0xf1, 0x81, 0x1d, 0xed, 0x77, 0x49, - 0x55, 0xe1, 0xa8, 0xad, 0xd4, 0x03, 0xb7, 0x46, 0x6a, 0x01, 0x09, 0xa1, 0x6a, 0xd5, 0x5e, 0x7a, - 0xb1, 0x36, 0x9b, 0xc5, 0xb1, 0x6a, 0x7b, 0x2d, 0xef, 0x3a, 0x32, 0xff, 0xa2, 0x3f, 0xab, 0x47, - 0x8e, 0x3d, 0x59, 0x15, 0xfc, 0x03, 0xdf, 0x2b, 0x55, 0x5e, 0xdb, 0x01, 0x21, 0x7a, 0xf2, 0xcc, - 0x7b, 0x6f, 0x76, 0xe6, 0x8d, 0x35, 0x60, 0xc8, 0x57, 0xe1, 0x34, 0xe1, 0x8c, 0xfb, 0xb1, 0x72, - 0xe2, 0x44, 0x28, 0x01, 0x91, 0xe4, 0xbe, 0x8e, 0x98, 0x08, 0x1c, 0xc9, 0x7d, 0xb6, 0xa4, 0x7e, - 0xe4, 0xf0, 0x55, 0x78, 0xb4, 0xef, 0x09, 0x4f, 0x68, 0x6a, 0x5a, 0x46, 0x95, 0x1e, 0x53, 0xd0, - 0xbe, 0x12, 0x1e, 0x44, 0xa0, 0x47, 0x17, 0x8b, 0x84, 0x4b, 0x89, 0x8c, 0xb1, 0x31, 0x31, 0x49, - 0x93, 0xc2, 0x03, 0xd0, 0x55, 0x22, 0xf6, 0x99, 0x44, 0xad, 0x71, 0x7b, 0x62, 0x92, 0x3a, 0x83, - 0x10, 0x74, 0x16, 0x54, 0x51, 0xd4, 0x1e, 0x1b, 0x93, 0x6d, 0xa2, 0x63, 0xb8, 0x0f, 0x36, 0xfd, - 0x68, 0xc1, 0x33, 0xd4, 0x19, 0x1b, 0x93, 0x01, 0xa9, 0x12, 0xfc, 0x67, 0x13, 0xf4, 0x48, 0x35, - 0x24, 0x7c, 0x0d, 0x7a, 0x2a, 0x73, 0xd5, 0x6d, 0xcc, 0x75, 0x9f, 0xc1, 0x0c, 0x16, 0xb9, 0xbd, - 0x73, 0x4b, 0xc3, 0xe0, 0x0c, 0xd7, 0x04, 0x26, 0x5d, 0x95, 0x7d, 0xb9, 0x8d, 0x39, 0xbc, 0x06, - 0x23, 0x96, 0x86, 0x69, 0x40, 0x95, 0xbf, 0xe2, 0xae, 0x47, 0xa5, 0x9b, 0x4a, 0xbe, 0x40, 0xad, - 0xb1, 0x31, 0xe9, 0xcc, 0xac, 0x22, 0xb7, 0x8f, 0xaa, 0xc2, 0x17, 0x44, 0x98, 0x0c, 0x1f, 0xd1, - 0x73, 0x2a, 0xbf, 0x4a, 0xbe, 0x80, 0x9f, 0xc0, 0x1e, 0x13, 0x91, 0x4a, 0x28, 0x53, 0x6e, 0xe3, - 0xb6, 0x1c, 0xdf, 0x9c, 0xfd, 0x57, 0xe4, 0xf6, 0x61, 0xfd, 0xd8, 0x33, 0x05, 0x26, 0xbb, 0x0d, - 0xf4, 0xa1, 0x5e, 0xc9, 0x7b, 0xd0, 0x57, 0x99, 0xbb, 0xa4, 0x72, 0xe9, 0x2e, 0x6b, 0xb3, 0xe6, - 0xec, 0xa0, 0xc8, 0x6d, 0xb8, 0x36, 0xd2, 0x90, 0x98, 0x98, 0x2a, 0xbb, 0xa0, 0x72, 0x79, 0xc1, - 0x33, 0xe8, 0x80, 0xad, 0xb5, 0x89, 0x4d, 0x6d, 0x62, 0x54, 0xe4, 0xf6, 0x6e, 0x55, 0xf4, 0x38, - 0x79, 0xcf, 0xab, 0xe7, 0xbd, 0x06, 0x23, 0x7e, 0x73, 0xc3, 0xd9, 0xda, 0x59, 0x9c, 0xf8, 0x8c, - 0xa3, 0xee, 0x73, 0xff, 0x2f, 0x88, 0x30, 0x19, 0xae, 0xd1, 0x73, 0x2a, 0x3f, 0x97, 0x18, 0x3c, - 0x03, 0xdb, 0xf3, 0x40, 0xb0, 0xef, 0x6e, 0x94, 0x86, 0x73, 0x9e, 0xa0, 0x9e, 0x7e, 0xe8, 0xb0, - 0xc8, 0xed, 0x51, 0xf5, 0xd0, 0x53, 0x16, 0x93, 0xbe, 0x4e, 0xaf, 0x75, 0x06, 0x2f, 0xc1, 0x50, - 0x25, 0x34, 0x92, 0x94, 0x29, 0x5f, 0x44, 0x6e, 0xf5, 0x9b, 0xb7, 0xf4, 0x2f, 0x3c, 0x2e, 0x72, - 0x1b, 0xd5, 0xce, 0x9f, 0x4b, 0x30, 0xd9, 0x7b, 0x82, 0x5d, 0x96, 0x10, 0x7c, 0x05, 0xba, 0x52, - 0x51, 0x95, 0x4a, 0x64, 0xea, 0xfa, 0x61, 0x91, 0xdb, 0x83, 0xaa, 0xbe, 0xc2, 0x31, 0xa9, 0x05, - 0xf0, 0x7f, 0xd0, 0xb9, 0x49, 0x44, 0x88, 0x80, 0x5e, 0xf1, 0x6e, 0x91, 0xdb, 0xfd, 0x4a, 0x58, - 0xa2, 0x98, 0x68, 0x12, 0x9e, 0x80, 0x96, 0x12, 0xa8, 0xaf, 0x25, 0x83, 0x22, 0xb7, 0xcd, 0x7a, - 0x16, 0x81, 0x49, 0x4b, 0x89, 0x72, 0xeb, 0xab, 0xd0, 0xe5, 0x49, 0x22, 0x12, 0xb4, 0xad, 0x45, - 0x4f, 0xb6, 0xde, 0x30, 0x98, 0xf4, 0x56, 0xe1, 0xc7, 0x32, 0x82, 0x6f, 0x40, 0x27, 0x10, 0x9e, - 0x44, 0x83, 0x71, 0x7b, 0xd2, 0x7f, 0x7b, 0xe2, 0xfc, 0xeb, 0xa0, 0x9c, 0x2b, 0xe1, 0x11, 0x2d, - 0x85, 0xc7, 0xc0, 0x2c, 0xbf, 0xb3, 0x40, 0x88, 0x10, 0xed, 0xe8, 0x83, 0x78, 0x04, 0x66, 0xe7, - 0x3f, 0xef, 0x2d, 0xe3, 0xee, 0xde, 0x32, 0x7e, 0xdf, 0x5b, 0xc6, 0x8f, 0x07, 0x6b, 0xe3, 0xee, - 0xc1, 0xda, 0xf8, 0xf5, 0x60, 0x6d, 0x7c, 0x3b, 0xf5, 0x7c, 0xb5, 0x4c, 0xe7, 0x0e, 0x13, 0xe1, - 0x54, 0x72, 0xff, 0xb4, 0xe9, 0xa3, 0x13, 0xdd, 0x68, 0x9a, 0x4d, 0xcb, 0x1b, 0x2f, 0xef, 0x42, - 0xce, 0xbb, 0x9a, 0x7f, 0xf7, 0x37, 0x00, 0x00, 0xff, 0xff, 0x24, 0x16, 0xa8, 0xf6, 0xf7, 0x03, - 0x00, 0x00, + // 628 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x54, 0xcf, 0x6b, 0xdb, 0x30, + 0x14, 0xae, 0xf3, 0xb3, 0x56, 0x9a, 0xb6, 0x51, 0x4a, 0x2b, 0xba, 0x36, 0x36, 0xda, 0xc5, 0x63, + 0xd4, 0x61, 0x1b, 0xec, 0xd0, 0xdb, 0x02, 0x5b, 0x5b, 0x28, 0x65, 0x88, 0xed, 0xb2, 0x8b, 0x51, + 0x14, 0xd5, 0x36, 0x8b, 0xad, 0x60, 0x29, 0xc1, 0x39, 0xed, 0x5f, 0xd8, 0x9f, 0xb5, 0x63, 0x8f, + 0x3b, 0x99, 0xd1, 0xfe, 0x07, 0xbe, 0x0f, 0x86, 0x65, 0x27, 0x29, 0xa5, 0x3b, 0xe5, 0xbd, 0xef, + 0xfb, 0x9e, 0xf4, 0xbe, 0x17, 0x3d, 0x83, 0x1e, 0x5f, 0x44, 0xc3, 0x84, 0x33, 0x1e, 0xce, 0x94, + 0x3b, 0x4b, 0x84, 0x12, 0x10, 0x49, 0x1e, 0xea, 0x88, 0x89, 0xa9, 0x2b, 0x79, 0xc8, 0x02, 0x1a, + 0xc6, 0x2e, 0x5f, 0x44, 0xc7, 0x07, 0xbe, 0xf0, 0x85, 0xa6, 0x86, 0x45, 0x54, 0xea, 0xf1, 0x0f, + 0x50, 0xbf, 0x16, 0x3e, 0x44, 0xa0, 0x4d, 0x27, 0x93, 0x84, 0x4b, 0x89, 0x0c, 0xdb, 0x70, 0x4c, + 0xb2, 0x4a, 0xe1, 0x21, 0x68, 0x29, 0x31, 0x0b, 0x99, 0x44, 0x35, 0xbb, 0xee, 0x98, 0xa4, 0xca, + 0x20, 0x04, 0x8d, 0x09, 0x55, 0x14, 0xd5, 0x6d, 0xc3, 0xd9, 0x21, 0x3a, 0x86, 0x07, 0xa0, 0x19, + 0xc6, 0x13, 0x9e, 0xa2, 0x86, 0x6d, 0x38, 0x5d, 0x52, 0x26, 0xf0, 0x04, 0x98, 0x72, 0x19, 0xab, + 0x80, 0xab, 0x90, 0xa1, 0xa6, 0x6d, 0x38, 0xdb, 0x64, 0x03, 0xe0, 0xbf, 0x4d, 0xd0, 0x26, 0xa5, + 0x05, 0xf8, 0x1a, 0xb4, 0x55, 0xea, 0xa9, 0xe5, 0x8c, 0xeb, 0x2e, 0xba, 0x23, 0x98, 0x67, 0xd6, + 0xee, 0x92, 0x46, 0xd3, 0x73, 0x5c, 0x11, 0x98, 0xb4, 0x54, 0xfa, 0x65, 0x39, 0xe3, 0xf0, 0x06, + 0xf4, 0xd9, 0x3c, 0x9a, 0x4f, 0xa9, 0x0a, 0x17, 0xdc, 0xf3, 0xa9, 0xf4, 0xe6, 0x92, 0x4f, 0x50, + 0xcd, 0x36, 0x9c, 0xc6, 0x68, 0x90, 0x67, 0xd6, 0x71, 0x59, 0xf8, 0x8c, 0x08, 0x93, 0xde, 0x06, + 0xbd, 0xa0, 0xf2, 0xab, 0xe4, 0x13, 0xf8, 0x09, 0xec, 0x33, 0x11, 0xab, 0x84, 0x32, 0xe5, 0xad, + 0x66, 0x51, 0x98, 0x33, 0x47, 0x2f, 0xf2, 0xcc, 0x3a, 0xaa, 0x0e, 0x7b, 0xa2, 0xc0, 0x64, 0x6f, + 0x05, 0x7d, 0xa8, 0x06, 0xf6, 0x1e, 0x74, 0x54, 0xea, 0x05, 0x54, 0x06, 0x5e, 0x50, 0x8d, 0xc2, + 0x1c, 0x1d, 0xe6, 0x99, 0x05, 0xd7, 0x46, 0x56, 0x24, 0x26, 0xa6, 0x4a, 0x2f, 0xa9, 0x0c, 0x2e, + 0x79, 0x0a, 0x5d, 0xb0, 0xbd, 0x36, 0xd1, 0xd4, 0x26, 0xfa, 0x79, 0x66, 0xed, 0x95, 0x45, 0x9b, + 0xce, 0xdb, 0x7e, 0xd5, 0xef, 0x0d, 0xe8, 0xf3, 0xdb, 0x5b, 0xce, 0xd6, 0xce, 0x66, 0x49, 0xc8, + 0x38, 0x6a, 0x3d, 0xf5, 0xff, 0x8c, 0x08, 0x93, 0xde, 0x1a, 0xbd, 0xa0, 0xf2, 0x73, 0x81, 0xc1, + 0x73, 0xb0, 0x33, 0x9e, 0x0a, 0xf6, 0xdd, 0x8b, 0xe7, 0xd1, 0x98, 0x27, 0xa8, 0xad, 0x0f, 0x3a, + 0xca, 0x33, 0xab, 0x5f, 0x1e, 0xf4, 0x98, 0xc5, 0xa4, 0xa3, 0xd3, 0x1b, 0x9d, 0xc1, 0x2b, 0xd0, + 0x53, 0x09, 0x8d, 0x25, 0x65, 0x2a, 0x14, 0xb1, 0x57, 0x3e, 0x82, 0x6d, 0xfd, 0x17, 0x9e, 0xe4, + 0x99, 0x85, 0x2a, 0xe7, 0x4f, 0x25, 0x98, 0xec, 0x3f, 0xc2, 0xae, 0xf4, 0x6b, 0x79, 0x05, 0x5a, + 0x52, 0x51, 0x35, 0x97, 0xc8, 0xd4, 0xf5, 0xbd, 0x3c, 0xb3, 0xba, 0x65, 0x7d, 0x89, 0x63, 0x52, + 0x09, 0xe0, 0x4b, 0xd0, 0xb8, 0x4d, 0x44, 0x84, 0x80, 0x1e, 0xf1, 0x5e, 0x9e, 0x59, 0x9d, 0x52, + 0x58, 0xa0, 0x98, 0x68, 0x12, 0x9e, 0x82, 0x9a, 0x12, 0xa8, 0xa3, 0x25, 0xdd, 0x3c, 0xb3, 0xcc, + 0xaa, 0x17, 0x81, 0x49, 0x4d, 0x89, 0x62, 0xea, 0x8b, 0xc8, 0xe3, 0x49, 0x22, 0x12, 0xb4, 0xa3, + 0x45, 0x8f, 0xa6, 0xbe, 0x62, 0x30, 0x69, 0x2f, 0xa2, 0x8f, 0x45, 0x04, 0xdf, 0x80, 0xc6, 0x54, + 0xf8, 0x12, 0x75, 0xed, 0xba, 0xd3, 0x79, 0x7b, 0xea, 0xfe, 0x6f, 0xdd, 0xdc, 0x6b, 0xe1, 0x13, + 0x2d, 0x2d, 0xde, 0x7f, 0xf1, 0x3b, 0x9a, 0x0a, 0x11, 0xa1, 0x5d, 0xbd, 0x2e, 0x1b, 0x60, 0x74, + 0xf1, 0xeb, 0x7e, 0x60, 0xdc, 0xdd, 0x0f, 0x8c, 0x3f, 0xf7, 0x03, 0xe3, 0xe7, 0xc3, 0x60, 0xeb, + 0xee, 0x61, 0xb0, 0xf5, 0xfb, 0x61, 0xb0, 0xf5, 0xed, 0xcc, 0x0f, 0x55, 0x30, 0x1f, 0xbb, 0x4c, + 0x44, 0x43, 0xc9, 0xc3, 0xb3, 0xd5, 0x3d, 0x3a, 0xd1, 0x17, 0x0d, 0xd3, 0x61, 0xf1, 0x05, 0x28, + 0xf6, 0x42, 0x8e, 0x5b, 0x9a, 0x7f, 0xf7, 0x2f, 0x00, 0x00, 0xff, 0xff, 0xbb, 0x40, 0xba, 0xc2, + 0x15, 0x04, 0x00, 0x00, } func (m *Log) Marshal() (dAtA []byte, err error) { @@ -309,6 +318,16 @@ func (m *Log) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Synthetic { + i-- + if m.Synthetic { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } if m.Index != 0 { i = encodeVarintReceipt(dAtA, i, uint64(m.Index)) i-- @@ -488,6 +507,9 @@ func (m *Log) Size() (n int) { if m.Index != 0 { n += 1 + sovReceipt(uint64(m.Index)) } + if m.Synthetic { + n += 2 + } return n } @@ -703,6 +725,26 @@ func (m *Log) Unmarshal(dAtA []byte) error { break } } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Synthetic", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowReceipt + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Synthetic = bool(v != 0) default: iNdEx = preIndex skippy, err := skipReceipt(dAtA[iNdEx:]) diff --git a/x/mint/module.go b/x/mint/module.go index d563d9221..661a61578 100644 --- a/x/mint/module.go +++ b/x/mint/module.go @@ -71,6 +71,17 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingCo return types.ValidateGenesis(data) } +// ValidateGenesisStream performs genesis state validation for the mint module in a streaming fashion. +func (am AppModuleBasic) ValidateGenesisStream(cdc codec.JSONCodec, config client.TxEncodingConfig, genesisCh <-chan json.RawMessage) error { + for genesis := range genesisCh { + err := am.ValidateGenesis(cdc, config, genesis) + if err != nil { + return err + } + } + return nil +} + // RegisterRESTRoutes registers the REST routes for the mint module. func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { rest.RegisterRoutes(clientCtx, rtr) @@ -155,6 +166,16 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw return cdc.MustMarshalJSON(gs) } +// ExportGenesisStream returns the mint module's exported genesis state as raw JSON bytes in a streaming fashion. +func (am AppModule) ExportGenesisStream(ctx sdk.Context, cdc codec.JSONCodec) <-chan json.RawMessage { + ch := make(chan json.RawMessage) + go func() { + ch <- am.ExportGenesis(ctx, cdc) + close(ch) + }() + return ch +} + // ConsensusVersion implements AppModule/ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 3 } diff --git a/x/oracle/module.go b/x/oracle/module.go index e69d68ad0..02c6f9cc0 100644 --- a/x/oracle/module.go +++ b/x/oracle/module.go @@ -68,6 +68,17 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingCo return types.ValidateGenesis(&data) } +// ValidateGenesisStream performs genesis state validation for the oracle module in a streaming fashion. +func (am AppModuleBasic) ValidateGenesisStream(cdc codec.JSONCodec, config client.TxEncodingConfig, genesisCh <-chan json.RawMessage) error { + for genesis := range genesisCh { + err := am.ValidateGenesis(cdc, config, genesis) + if err != nil { + return err + } + } + return nil +} + // RegisterRESTRoutes registers the REST routes for the oracle module. func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { rest.RegisterRoutes(clientCtx, rtr) @@ -163,6 +174,16 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw return cdc.MustMarshalJSON(gs) } +// ExportGenesisStream returns the oracle module's exported genesis state as raw JSON bytes in a streaming fashion. +func (am AppModule) ExportGenesisStream(ctx sdk.Context, cdc codec.JSONCodec) <-chan json.RawMessage { + ch := make(chan json.RawMessage) + go func() { + ch <- am.ExportGenesis(ctx, cdc) + close(ch) + }() + return ch +} + // ConsensusVersion implements AppModule/ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 6 } diff --git a/x/tokenfactory/module.go b/x/tokenfactory/module.go index 89a97560d..c9279e187 100644 --- a/x/tokenfactory/module.go +++ b/x/tokenfactory/module.go @@ -172,6 +172,27 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw return cdc.MustMarshalJSON(genState) } +// ExportGenesisStream returns the tokenfactory module's exported genesis state as raw JSON bytes in a streaming fashion. +func (am AppModule) ExportGenesisStream(ctx sdk.Context, cdc codec.JSONCodec) <-chan json.RawMessage { + ch := make(chan json.RawMessage) + go func() { + ch <- am.ExportGenesis(ctx, cdc) + close(ch) + }() + return ch +} + +// ValidateGenesisStream performs genesis state validation for the x/tokenfactory module in a streaming fashion. +func (am AppModuleBasic) ValidateGenesisStream(cdc codec.JSONCodec, config client.TxEncodingConfig, genesisCh <-chan json.RawMessage) error { + for genesis := range genesisCh { + err := am.ValidateGenesis(cdc, config, genesis) + if err != nil { + return err + } + } + return nil +} + // ConsensusVersion implements ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 4 }