From f1f2017c137117c36931d76c767262a6534f0c25 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Mon, 9 Jun 2025 18:53:30 +0900 Subject: [PATCH 01/19] feat(precompiles): add BalanceHandler to handle native balance change --- precompiles/common/balance_handler.go | 102 +++++++++++++++++++++++ precompiles/common/precompile.go | 19 ++--- precompiles/distribution/distribution.go | 9 ++ precompiles/erc20/erc20.go | 12 +++ precompiles/evidence/evidence.go | 8 ++ precompiles/gov/gov.go | 10 +++ precompiles/ics20/ics20.go | 9 ++ precompiles/slashing/slashing.go | 8 ++ precompiles/staking/staking.go | 100 ++++++++++++---------- precompiles/staking/tx.go | 15 ---- precompiles/werc20/werc20.go | 9 ++ 11 files changed, 229 insertions(+), 72 deletions(-) create mode 100644 precompiles/common/balance_handler.go diff --git a/precompiles/common/balance_handler.go b/precompiles/common/balance_handler.go new file mode 100644 index 000000000..3a929bb82 --- /dev/null +++ b/precompiles/common/balance_handler.go @@ -0,0 +1,102 @@ +package common + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/holiman/uint256" + + "github.com/cosmos/evm/utils" + "github.com/cosmos/evm/x/vm/statedb" + evmtypes "github.com/cosmos/evm/x/vm/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// BalanceHandler wraps bank accesses and records differences automatically. +type BalanceHandler struct { + prevEventsLen int +} + +func NewBalanceHandler() *BalanceHandler { + return &BalanceHandler{ + prevEventsLen: 0, + } +} + +// Begin records initial balances for the provided addresses. +func (bh *BalanceHandler) BeforeBalanceChange(ctx sdk.Context) { + bh.prevEventsLen = len(ctx.EventManager().Events()) +} + +// End compares balances and populates the journal. +func (bh *BalanceHandler) AfterBalanceChange(ctx sdk.Context, stateDB *statedb.StateDB) error { + events := ctx.EventManager().Events() + + for _, event := range events[bh.prevEventsLen:] { + switch event.Type { + case banktypes.EventTypeCoinSpent: + spenderHexAddr, err := parseHexAddress(event, banktypes.AttributeKeySpender) + if err != nil { + return fmt.Errorf("failed to parse spender address from event %q: %w", banktypes.EventTypeCoinSpent, err) + } + + amount, err := parseAmount(event) + if err != nil { + return fmt.Errorf("failed to parse amount from event %q: %w", banktypes.EventTypeCoinSpent, err) + } + + stateDB.SubBalance(spenderHexAddr, amount, tracing.BalanceChangeUnspecified) + + case banktypes.EventTypeCoinReceived: + receiverHexAddr, err := parseHexAddress(event, banktypes.AttributeKeyReceiver) + if err != nil { + return fmt.Errorf("failed to parse receiver address from event %q: %w", banktypes.EventTypeCoinReceived, err) + } + + amount, err := parseAmount(event) + if err != nil { + return fmt.Errorf("failed to parse amount from event %q: %w", banktypes.EventTypeCoinReceived, err) + } + + stateDB.AddBalance(receiverHexAddr, amount, tracing.BalanceChangeUnspecified) + } + } + + return nil +} + +func parseHexAddress(event sdk.Event, key string) (common.Address, error) { + attr, ok := event.GetAttribute(key) + if !ok { + return common.Address{}, fmt.Errorf("event %q missing attribute %q", event.Type, key) + } + + accAddr, err := sdk.AccAddressFromBech32(attr.Value) + if err != nil { + return common.Address{}, fmt.Errorf("invalid address %q: %w", attr.Value, err) + } + + return common.Address(accAddr.Bytes()), nil +} + +func parseAmount(event sdk.Event) (*uint256.Int, error) { + amountAttr, ok := event.GetAttribute(sdk.AttributeKeyAmount) + if !ok { + return nil, fmt.Errorf("event %q missing attribute %q", banktypes.EventTypeCoinSpent, sdk.AttributeKeyAmount) + } + + amountCoins, err := sdk.ParseCoinsNormalized(amountAttr.Value) + if err != nil { + return nil, fmt.Errorf("failed to parse coins from %q: %w", amountAttr.Value, err) + } + + amountBigInt := amountCoins.AmountOf(evmtypes.GetEVMCoinDenom()).BigInt() + amount, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(amountBigInt)) + if err != nil { + return nil, fmt.Errorf("failed to convert coin amount to Uint256: %w", err) + } + return amount, nil +} diff --git a/precompiles/common/precompile.go b/precompiles/common/precompile.go index 4d1436a44..3a846026d 100644 --- a/precompiles/common/precompile.go +++ b/precompiles/common/precompile.go @@ -24,6 +24,7 @@ type Precompile struct { TransientKVGasConfig storetypes.GasConfig address common.Address journalEntries []BalanceChangeEntry + balanceTracker *BalanceHandler } // Operation is a type that defines if the precompile call @@ -193,17 +194,6 @@ func HandleGasError(ctx sdk.Context, contract *vm.Contract, initialGas storetype // and precompileCall entries on the stateDB journal // This allows to revert the call changes within an evm tx func (p Precompile) AddJournalEntries(stateDB *statedb.StateDB, s Snapshot) error { - for _, entry := range p.journalEntries { - switch entry.Op { - case Sub: - // add the corresponding balance change to the journal - stateDB.SubBalance(entry.Account, entry.Amount, tracing.BalanceChangeUnspecified) - case Add: - // add the corresponding balance change to the journal - stateDB.AddBalance(entry.Account, entry.Amount, tracing.BalanceChangeUnspecified) - } - } - return stateDB.AddPrecompileFn(p.Address(), s.MultiStore, s.Events) } @@ -267,3 +257,10 @@ func (p Precompile) standardCallData(contract *vm.Contract) (method *abi.Method, return method, nil } + +func (p *Precompile) GetBalanceHandler() *BalanceHandler { + if p.balanceTracker == nil { + p.balanceTracker = NewBalanceHandler() + } + return p.balanceTracker +} diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index e111a72cb..84530f2a7 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -90,6 +90,9 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, err } + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. // It avoids panics and returns the out of gas error so the EVM can continue gracefully. defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)() @@ -141,6 +144,12 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, vm.ErrOutOfGas } + // Process the native balance changes after the method execution. + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + // TODO: Should be called before calling keeper methods that modify the stateDB. if err := p.AddJournalEntries(stateDB, snapshot); err != nil { return nil, err } diff --git a/precompiles/erc20/erc20.go b/precompiles/erc20/erc20.go index 5715cd058..23453c46f 100644 --- a/precompiles/erc20/erc20.go +++ b/precompiles/erc20/erc20.go @@ -141,6 +141,9 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, err } + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. // It avoids panics and returns the out of gas error so the EVM can continue gracefully. defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)() @@ -156,9 +159,18 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { return nil, vm.ErrOutOfGas } + + // Process the native balance changes after the method execution. + err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB) + if err != nil { + return nil, err + } + + // TODO: Should be called before calling keeper methods that modify the stateDB. if err := p.AddJournalEntries(stateDB, snapshot); err != nil { return nil, err } + return bz, nil }) } diff --git a/precompiles/evidence/evidence.go b/precompiles/evidence/evidence.go index 86398396f..c390e399e 100644 --- a/precompiles/evidence/evidence.go +++ b/precompiles/evidence/evidence.go @@ -87,6 +87,9 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, err } + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. // It avoids panics and returns the out of gas error so the EVM can continue gracefully. defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)() @@ -115,6 +118,11 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, vm.ErrOutOfGas } + // Process the native balance changes after the method execution. + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + if err := p.AddJournalEntries(stateDB, snapshot); err != nil { return nil, err } diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index 7005f9542..9d2486dc6 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -91,6 +91,9 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, err } + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. // It avoids panics and returns the out of gas error so the EVM can continue gracefully. defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)() @@ -142,6 +145,13 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, vm.ErrOutOfGas } + // Process the native balance changes after the method execution. + err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB) + if err != nil { + return nil, err + } + + // TODO: Should be called before calling keeper methods that modify the stateDB. if err := p.AddJournalEntries(stateDB, snapshot); err != nil { return nil, err } diff --git a/precompiles/ics20/ics20.go b/precompiles/ics20/ics20.go index 8c8b5adbc..b98bb5da0 100644 --- a/precompiles/ics20/ics20.go +++ b/precompiles/ics20/ics20.go @@ -94,6 +94,9 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, err } + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. // It avoids panics and returns the out of gas error so the EVM can continue gracefully. defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)() @@ -124,6 +127,12 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, vm.ErrOutOfGas } + // Process the native balance changes after the method execution. + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + // TODO: Should be called before calling keeper methods that modify the stateDB. if err := p.AddJournalEntries(stateDB, snapshot); err != nil { return nil, err } diff --git a/precompiles/slashing/slashing.go b/precompiles/slashing/slashing.go index d053e3094..a211d8297 100644 --- a/precompiles/slashing/slashing.go +++ b/precompiles/slashing/slashing.go @@ -87,6 +87,9 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, err } + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. // It avoids panics and returns the out of gas error so the EVM can continue gracefully. defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)() @@ -115,6 +118,11 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, vm.ErrOutOfGas } + // Process the native balance changes after the method execution. + if err := p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + if err := p.AddJournalEntries(stateDB, snapshot); err != nil { return nil, err } diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 88ab9eef9..e57821c3a 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -86,56 +86,64 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, err } + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. // It avoids panics and returns the out of gas error so the EVM can continue gracefully. defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)() - return p.RunAtomic(snapshot, stateDB, func() ([]byte, error) { - switch method.Name { - // Staking transactions - case CreateValidatorMethod: - bz, err = p.CreateValidator(ctx, contract, stateDB, method, args) - case EditValidatorMethod: - bz, err = p.EditValidator(ctx, contract, stateDB, method, args) - case DelegateMethod: - bz, err = p.Delegate(ctx, contract, stateDB, method, args) - case UndelegateMethod: - bz, err = p.Undelegate(ctx, contract, stateDB, method, args) - case RedelegateMethod: - bz, err = p.Redelegate(ctx, contract, stateDB, method, args) - case CancelUnbondingDelegationMethod: - bz, err = p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) - // Staking queries - case DelegationMethod: - bz, err = p.Delegation(ctx, contract, method, args) - case UnbondingDelegationMethod: - bz, err = p.UnbondingDelegation(ctx, contract, method, args) - case ValidatorMethod: - bz, err = p.Validator(ctx, method, contract, args) - case ValidatorsMethod: - bz, err = p.Validators(ctx, method, contract, args) - case RedelegationMethod: - bz, err = p.Redelegation(ctx, method, contract, args) - case RedelegationsMethod: - bz, err = p.Redelegations(ctx, method, contract, args) - } - - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - if err := p.AddJournalEntries(stateDB, snapshot); err != nil { - return nil, err - } - - return bz, nil - }) + switch method.Name { + // Staking transactions + case CreateValidatorMethod: + bz, err = p.CreateValidator(ctx, contract, stateDB, method, args) + case EditValidatorMethod: + bz, err = p.EditValidator(ctx, contract, stateDB, method, args) + case DelegateMethod: + bz, err = p.Delegate(ctx, contract, stateDB, method, args) + case UndelegateMethod: + bz, err = p.Undelegate(ctx, contract, stateDB, method, args) + case RedelegateMethod: + bz, err = p.Redelegate(ctx, contract, stateDB, method, args) + case CancelUnbondingDelegationMethod: + bz, err = p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) + // Staking queries + case DelegationMethod: + bz, err = p.Delegation(ctx, contract, method, args) + case UnbondingDelegationMethod: + bz, err = p.UnbondingDelegation(ctx, contract, method, args) + case ValidatorMethod: + bz, err = p.Validator(ctx, method, contract, args) + case ValidatorsMethod: + bz, err = p.Validators(ctx, method, contract, args) + case RedelegationMethod: + bz, err = p.Redelegation(ctx, method, contract, args) + case RedelegationsMethod: + bz, err = p.Redelegations(ctx, method, contract, args) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + // TODO: Should be called before calling keeper methods that modify the stateDB. + if err := p.AddJournalEntries(stateDB, snapshot); err != nil { + return nil, err + } + + return bz, nil + } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/staking/tx.go b/precompiles/staking/tx.go index ce61ab95e..f84bff808 100644 --- a/precompiles/staking/tx.go +++ b/precompiles/staking/tx.go @@ -8,8 +8,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" - "github.com/cosmos/evm/utils" - evmtypes "github.com/cosmos/evm/x/vm/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" @@ -175,19 +173,6 @@ func (p *Precompile) Delegate( return nil, err } - if msg.Amount.Denom == evmtypes.GetEVMCoinDenom() { - // NOTE: This ensures that the changes in the bank keeper are correctly mirrored to the EVM stateDB - // when calling the precompile from a smart contract - // This prevents the stateDB from overwriting the changed balance in the bank keeper when committing the EVM state. - - // Need to scale the amount to 18 decimals for the EVM balance change entry - scaledAmt, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(msg.Amount.Amount.BigInt())) - if err != nil { - return nil, err - } - p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(delegatorHexAddr, scaledAmt, cmn.Sub)) - } - return method.Outputs.Pack(true) } diff --git a/precompiles/werc20/werc20.go b/precompiles/werc20/werc20.go index a0b88d0e4..36a27f8c0 100644 --- a/precompiles/werc20/werc20.go +++ b/precompiles/werc20/werc20.go @@ -112,6 +112,9 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, err } + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + // This handles any out of gas errors that may occur during the execution of // a precompile tx or query. It avoids panics and returns the out of gas error so // the EVM can continue gracefully. @@ -140,6 +143,12 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, vm.ErrOutOfGas } + // Process the native balance changes after the method execution. + if err := p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + // TODO: Should be called before calling keeper methods that modify the stateDB. if err := p.AddJournalEntries(stateDB, snapshot); err != nil { return nil, err } From 04fd359c7ad0c64d15a272a32162f2ebc404b82c Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Mon, 9 Jun 2025 19:09:50 +0900 Subject: [PATCH 02/19] refactor: remove parts of calling SetBalanceChangeEntries --- evmd/app.go | 9 ----- precompiles/common/precompile.go | 9 ----- precompiles/distribution/tx.go | 66 -------------------------------- precompiles/erc20/tx.go | 14 ------- precompiles/gov/tx.go | 59 ---------------------------- precompiles/ics20/tx.go | 23 ----------- precompiles/werc20/tx.go | 8 ---- 7 files changed, 188 deletions(-) diff --git a/evmd/app.go b/evmd/app.go index ba72b9728..bcacada7a 100644 --- a/evmd/app.go +++ b/evmd/app.go @@ -471,15 +471,6 @@ func NewExampleApp( runtime.ProvideCometInfoService(), ) // If evidence needs to be handled for the app, set routes in router here and seal - // Note: The evidence precompile allows evidence to be submitted through an EVM transaction. - // If you implement a custom evidence handler in the router that changes token balances (e.g. penalizing - // addresses, deducting fees, etc.), be aware that the precompile logic (e.g. SetBalanceChangeEntries) - // must be properly integrated to reflect these balance changes in the EVM state. Otherwise, there is a risk - // of desynchronization between the Cosmos SDK state and the EVM state when evidence is submitted via the EVM. - // - // For example, if your custom evidence handler deducts tokens from a user’s account, ensure that the evidence - // precompile also applies these deductions through the EVM’s balance tracking. Failing to do so may cause - // inconsistencies in reported balances and break state synchronization. app.EvidenceKeeper = *evidenceKeeper // Cosmos EVM keepers diff --git a/precompiles/common/precompile.go b/precompiles/common/precompile.go index 3a846026d..fdef9eb8d 100644 --- a/precompiles/common/precompile.go +++ b/precompiles/common/precompile.go @@ -23,7 +23,6 @@ type Precompile struct { KvGasConfig storetypes.GasConfig TransientKVGasConfig storetypes.GasConfig address common.Address - journalEntries []BalanceChangeEntry balanceTracker *BalanceHandler } @@ -197,14 +196,6 @@ func (p Precompile) AddJournalEntries(stateDB *statedb.StateDB, s Snapshot) erro return stateDB.AddPrecompileFn(p.Address(), s.MultiStore, s.Events) } -// SetBalanceChangeEntries sets the balanceChange entries -// as the journalEntries field of the precompile. -// These entries will be added to the stateDB's journal -// when calling the AddJournalEntries function -func (p *Precompile) SetBalanceChangeEntries(entries ...BalanceChangeEntry) { - p.journalEntries = entries -} - func (p Precompile) Address() common.Address { return p.address } diff --git a/precompiles/distribution/tx.go b/precompiles/distribution/tx.go index 0cee9f17f..1b87061e7 100644 --- a/precompiles/distribution/tx.go +++ b/precompiles/distribution/tx.go @@ -6,11 +6,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - "github.com/holiman/uint256" cmn "github.com/cosmos/evm/precompiles/common" - "github.com/cosmos/evm/utils" - evmtypes "github.com/cosmos/evm/x/vm/types" sdk "github.com/cosmos/cosmos-sdk/types" distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" @@ -82,20 +79,6 @@ func (p *Precompile) ClaimRewards( totalCoins = totalCoins.Add(coins...) } - withdrawerHexAddr, err := p.getWithdrawerHexAddr(ctx, delegatorAddr) - if err != nil { - return nil, err - } - - convertedAmount, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(totalCoins.AmountOf(evmtypes.GetEVMCoinDenom()).BigInt())) - if err != nil { - return nil, err - } - // check if converted amount is greater than zero - if convertedAmount.Cmp(uint256.NewInt(0)) == 1 { - p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(withdrawerHexAddr, convertedAmount, cmn.Add)) - } - if err := p.EmitClaimRewardsEvent(ctx, stateDB, delegatorAddr, totalCoins); err != nil { return nil, err } @@ -157,21 +140,6 @@ func (p *Precompile) WithdrawDelegatorReward( return nil, err } - // rewards go to the withdrawer address - withdrawerHexAddr, err := p.getWithdrawerHexAddr(ctx, delegatorHexAddr) - if err != nil { - return nil, err - } - - convertedAmount, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(res.Amount.AmountOf(evmtypes.GetEVMCoinDenom()).BigInt())) - if err != nil { - return nil, err - } - // check if converted amount is greater than zero - if convertedAmount.Cmp(uint256.NewInt(0)) == 1 { - p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(withdrawerHexAddr, convertedAmount, cmn.Add)) - } - if err = p.EmitWithdrawDelegatorRewardEvent(ctx, stateDB, delegatorHexAddr, msg.ValidatorAddress, res.Amount); err != nil { return nil, err } @@ -203,21 +171,6 @@ func (p *Precompile) WithdrawValidatorCommission( return nil, err } - // commissions go to the withdrawer address - withdrawerHexAddr, err := p.getWithdrawerHexAddr(ctx, validatorHexAddr) - if err != nil { - return nil, err - } - - convertedAmount, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(res.Amount.AmountOf(evmtypes.GetEVMCoinDenom()).BigInt())) - if err != nil { - return nil, err - } - // check if converted amount is greater than zero - if convertedAmount.Cmp(uint256.NewInt(0)) == 1 { - p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(withdrawerHexAddr, convertedAmount, cmn.Add)) - } - if err = p.EmitWithdrawValidatorCommissionEvent(ctx, stateDB, msg.ValidatorAddress, res.Amount); err != nil { return nil, err } @@ -249,15 +202,6 @@ func (p *Precompile) FundCommunityPool( return nil, err } - convertedAmount, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(msg.Amount.AmountOf(evmtypes.GetEVMCoinDenom()).BigInt())) - if err != nil { - return nil, err - } - // check if converted amount is greater than zero - if convertedAmount.Cmp(uint256.NewInt(0)) == 1 { - p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(depositorHexAddr, convertedAmount, cmn.Sub)) - } - if err = p.EmitFundCommunityPoolEvent(ctx, stateDB, depositorHexAddr, msg.Amount); err != nil { return nil, err } @@ -289,16 +233,6 @@ func (p *Precompile) DepositValidatorRewardsPool( if err != nil { return nil, err } - if found, evmCoinAmount := msg.Amount.Find(evmtypes.GetEVMCoinDenom()); found { - convertedAmount, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(evmCoinAmount.Amount.BigInt())) - if err != nil { - return nil, err - } - // check if converted amount is greater than zero - if convertedAmount.Cmp(uint256.NewInt(0)) == 1 { - p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(depositorHexAddr, convertedAmount, cmn.Sub)) - } - } if err = p.EmitDepositValidatorRewardsPoolEvent(ctx, stateDB, depositorHexAddr, msg.ValidatorAddress, msg.Amount); err != nil { return nil, err diff --git a/precompiles/erc20/tx.go b/precompiles/erc20/tx.go index 365e0cf12..05be1a11a 100644 --- a/precompiles/erc20/tx.go +++ b/precompiles/erc20/tx.go @@ -7,10 +7,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - cmn "github.com/cosmos/evm/precompiles/common" - "github.com/cosmos/evm/utils" - evmtypes "github.com/cosmos/evm/x/vm/types" - "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" @@ -125,16 +121,6 @@ func (p *Precompile) transfer( return nil, ConvertErrToERC20Error(err) } - evmDenom := evmtypes.GetEVMCoinDenom() - if p.tokenPair.Denom == evmDenom { - convertedAmount, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(amount)) - if err != nil { - return nil, err - } - p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(from, convertedAmount, cmn.Sub), - cmn.NewBalanceChangeEntry(to, convertedAmount, cmn.Add)) - } - if err = p.EmitTransferEvent(ctx, stateDB, from, to, amount); err != nil { return nil, err } diff --git a/precompiles/gov/tx.go b/precompiles/gov/tx.go index ca2d24d78..572381f95 100644 --- a/precompiles/gov/tx.go +++ b/precompiles/gov/tx.go @@ -5,13 +5,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/core/vm" - "github.com/holiman/uint256" cmn "github.com/cosmos/evm/precompiles/common" - "github.com/cosmos/evm/utils" - evmtypes "github.com/cosmos/evm/x/vm/types" - - "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" @@ -53,15 +48,6 @@ func (p *Precompile) SubmitProposal( return nil, err } - deposit := msg.InitialDeposit - convertedAmount, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(deposit.AmountOf(evmtypes.GetEVMCoinDenom()).BigInt())) - if err != nil { - return nil, err - } - if convertedAmount.Cmp(uint256.NewInt(0)) == 1 { - p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(proposerHexAddr, convertedAmount, cmn.Sub)) - } - if err = p.EmitSubmitProposalEvent(ctx, stateDB, proposerHexAddr, res.ProposalId); err != nil { return nil, err } @@ -90,18 +76,6 @@ func (p *Precompile) Deposit( if _, err = govkeeper.NewMsgServerImpl(&p.govKeeper).Deposit(ctx, msg); err != nil { return nil, err } - for _, coin := range msg.Amount { - if coin.Denom != evmtypes.GetEVMCoinDenom() { - continue - } - convertedAmount, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(coin.Amount.BigInt())) - if err != nil { - return nil, err - } - if convertedAmount.Cmp(uint256.NewInt(0)) == 1 { - p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(depositorHexAddr, convertedAmount, cmn.Sub)) - } - } if err = p.EmitDepositEvent(ctx, stateDB, depositorHexAddr, msg.ProposalId, msg.Amount); err != nil { return nil, err @@ -128,43 +102,10 @@ func (p *Precompile) CancelProposal( return nil, fmt.Errorf(cmn.ErrRequesterIsNotMsgSender, msgSender.String(), proposerHexAddr.String()) } - // pre-calculate the remaining deposit - govParams, err := p.govKeeper.Params.Get(ctx) - if err != nil { - return nil, err - } - cancelRate, err := math.LegacyNewDecFromStr(govParams.ProposalCancelRatio) - if err != nil { - return nil, err - } - deposits, err := p.govKeeper.GetDeposits(ctx, msg.ProposalId) - if err != nil { - return nil, err - } - var remaninig math.Int - for _, deposit := range deposits { - if deposit.Depositor != sdk.AccAddress(proposerHexAddr.Bytes()).String() { - continue - } - for _, coin := range deposit.Amount { - if coin.Denom == evmtypes.GetEVMCoinDenom() { - cancelFee := coin.Amount.ToLegacyDec().Mul(cancelRate).TruncateInt() - remaninig = coin.Amount.Sub(cancelFee) - } - } - } if _, err = govkeeper.NewMsgServerImpl(&p.govKeeper).CancelProposal(ctx, msg); err != nil { return nil, err } - convertedAmount, err := utils.Uint256FromBigInt(evmtypes.ConvertAmountTo18DecimalsBigInt(remaninig.BigInt())) - if err != nil { - return nil, err - } - if convertedAmount.Cmp(uint256.NewInt(0)) == 1 { - p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(proposerHexAddr, convertedAmount, cmn.Add)) - } - if err = p.EmitCancelProposalEvent(ctx, stateDB, proposerHexAddr, msg.ProposalId); err != nil { return nil, err } diff --git a/precompiles/ics20/tx.go b/precompiles/ics20/tx.go index c2019b6fe..b0f66a5d9 100644 --- a/precompiles/ics20/tx.go +++ b/precompiles/ics20/tx.go @@ -4,13 +4,9 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" - "github.com/cosmos/evm/utils" - evmtypes "github.com/cosmos/evm/x/vm/types" - transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v10/modules/core/24-host" @@ -69,25 +65,6 @@ func (p *Precompile) Transfer( return nil, err } - evmDenom := evmtypes.GetEVMCoinDenom() - if msg.Token.Denom == evmDenom { - // escrow address is also changed on this tx, and it is not a module account - // so we need to account for this on the UpdateDirties - escrowAccAddress := transfertypes.GetEscrowAddress(msg.SourcePort, msg.SourceChannel) - escrowHexAddr := common.BytesToAddress(escrowAccAddress) - // NOTE: This ensures that the changes in the bank keeper are correctly mirrored to the EVM stateDB - // when calling the precompile from another smart contract. - // This prevents the stateDB from overwriting the changed balance in the bank keeper when committing the EVM state. - amt, err := utils.Uint256FromBigInt(msg.Token.Amount.BigInt()) - if err != nil { - return nil, err - } - p.SetBalanceChangeEntries( - cmn.NewBalanceChangeEntry(sender, amt, cmn.Sub), - cmn.NewBalanceChangeEntry(escrowHexAddr, amt, cmn.Add), - ) - } - if err = EmitIBCTransferEvent( ctx, stateDB, diff --git a/precompiles/werc20/tx.go b/precompiles/werc20/tx.go index 6ba36c0ab..4990e98a1 100644 --- a/precompiles/werc20/tx.go +++ b/precompiles/werc20/tx.go @@ -6,7 +6,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" - cmn "github.com/cosmos/evm/precompiles/common" evmtypes "github.com/cosmos/evm/x/vm/types" "cosmossdk.io/math" @@ -49,13 +48,6 @@ func (p Precompile) Deposit( return nil, err } - // Add the entries to the statedb journal since the function signature of - // the associated Solidity interface payable. - p.SetBalanceChangeEntries( - cmn.NewBalanceChangeEntry(caller, depositedAmount, cmn.Add), - cmn.NewBalanceChangeEntry(p.Address(), depositedAmount, cmn.Sub), - ) - if err := p.EmitDepositEvent(ctx, stateDB, caller, depositedAmount.ToBig()); err != nil { return nil, err } From 723754e5007b58cdc56dd20521ddfdd421483cfa Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Mon, 9 Jun 2025 20:07:46 +0900 Subject: [PATCH 03/19] chore: fix lint --- precompiles/staking/staking.go | 1 - 1 file changed, 1 deletion(-) diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index e57821c3a..7faa766fa 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -143,7 +143,6 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ } return bz, nil - } // IsTransaction checks if the given method name corresponds to a transaction or query. From aa2e71f071d216d26554ddf6512f845bcf340e1f Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Mon, 9 Jun 2025 21:13:33 +0900 Subject: [PATCH 04/19] chore(precompiles/distribution): remove unused helper function --- precompiles/distribution/tx.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/precompiles/distribution/tx.go b/precompiles/distribution/tx.go index 1b87061e7..5cda94a93 100644 --- a/precompiles/distribution/tx.go +++ b/precompiles/distribution/tx.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -240,14 +239,3 @@ func (p *Precompile) DepositValidatorRewardsPool( return method.Outputs.Pack(true) } - -// getWithdrawerHexAddr is a helper function to get the hex address -// of the withdrawer for the specified account address -func (p Precompile) getWithdrawerHexAddr(ctx sdk.Context, delegatorAddr common.Address) (common.Address, error) { - withdrawerAccAddr, err := p.distributionKeeper.GetDelegatorWithdrawAddr(ctx, delegatorAddr.Bytes()) - if err != nil { - return common.Address{}, err - } - - return common.BytesToAddress(withdrawerAccAddr), nil -} From 794089eefc8c6592866b7d397cccd2d274cf6aa5 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Mon, 9 Jun 2025 21:19:10 +0900 Subject: [PATCH 05/19] chore(precompiles): modify comments --- precompiles/common/balance_handler.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/precompiles/common/balance_handler.go b/precompiles/common/balance_handler.go index 3a929bb82..79f5f5b34 100644 --- a/precompiles/common/balance_handler.go +++ b/precompiles/common/balance_handler.go @@ -15,23 +15,28 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) -// BalanceHandler wraps bank accesses and records differences automatically. +// BalanceHandler is a struct that handles balance changes in the Cosmos SDK context. type BalanceHandler struct { prevEventsLen int } +// NewBalanceHandler creates a new BalanceHandler instance. func NewBalanceHandler() *BalanceHandler { return &BalanceHandler{ prevEventsLen: 0, } } -// Begin records initial balances for the provided addresses. +// BeforeBalanceChange is called before any balance changes by precompile methods. +// It records the current number of events in the context to later process balance changes +// using the recorded events. func (bh *BalanceHandler) BeforeBalanceChange(ctx sdk.Context) { bh.prevEventsLen = len(ctx.EventManager().Events()) } -// End compares balances and populates the journal. +// AfterBalanceChange processes the recorded events and updates the stateDB accordingly. +// It handles the bank events for coin spent and coin received, updating the balances +// of the spender and receiver addresses respectively. func (bh *BalanceHandler) AfterBalanceChange(ctx sdk.Context, stateDB *statedb.StateDB) error { events := ctx.EventManager().Events() From 958f7bd14d1e29b234898fc26ad10370b7356e56 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Mon, 9 Jun 2025 21:30:22 +0900 Subject: [PATCH 06/19] chore: restore modification to be applied later --- precompiles/staking/staking.go | 102 +++++++++++++++++---------------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 7faa766fa..4a72de5c7 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -93,56 +93,58 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ // It avoids panics and returns the out of gas error so the EVM can continue gracefully. defer cmn.HandleGasError(ctx, contract, initialGas, &err, stateDB, snapshot)() - switch method.Name { - // Staking transactions - case CreateValidatorMethod: - bz, err = p.CreateValidator(ctx, contract, stateDB, method, args) - case EditValidatorMethod: - bz, err = p.EditValidator(ctx, contract, stateDB, method, args) - case DelegateMethod: - bz, err = p.Delegate(ctx, contract, stateDB, method, args) - case UndelegateMethod: - bz, err = p.Undelegate(ctx, contract, stateDB, method, args) - case RedelegateMethod: - bz, err = p.Redelegate(ctx, contract, stateDB, method, args) - case CancelUnbondingDelegationMethod: - bz, err = p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) - // Staking queries - case DelegationMethod: - bz, err = p.Delegation(ctx, contract, method, args) - case UnbondingDelegationMethod: - bz, err = p.UnbondingDelegation(ctx, contract, method, args) - case ValidatorMethod: - bz, err = p.Validator(ctx, method, contract, args) - case ValidatorsMethod: - bz, err = p.Validators(ctx, method, contract, args) - case RedelegationMethod: - bz, err = p.Redelegation(ctx, method, contract, args) - case RedelegationsMethod: - bz, err = p.Redelegations(ctx, method, contract, args) - } - - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - // Process the native balance changes after the method execution. - if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { - return nil, err - } - - // TODO: Should be called before calling keeper methods that modify the stateDB. - if err := p.AddJournalEntries(stateDB, snapshot); err != nil { - return nil, err - } - - return bz, nil + return p.RunAtomic(snapshot, stateDB, func() ([]byte, error) { + switch method.Name { + // Staking transactions + case CreateValidatorMethod: + bz, err = p.CreateValidator(ctx, contract, stateDB, method, args) + case EditValidatorMethod: + bz, err = p.EditValidator(ctx, contract, stateDB, method, args) + case DelegateMethod: + bz, err = p.Delegate(ctx, contract, stateDB, method, args) + case UndelegateMethod: + bz, err = p.Undelegate(ctx, contract, stateDB, method, args) + case RedelegateMethod: + bz, err = p.Redelegate(ctx, contract, stateDB, method, args) + case CancelUnbondingDelegationMethod: + bz, err = p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) + // Staking queries + case DelegationMethod: + bz, err = p.Delegation(ctx, contract, method, args) + case UnbondingDelegationMethod: + bz, err = p.UnbondingDelegation(ctx, contract, method, args) + case ValidatorMethod: + bz, err = p.Validator(ctx, method, contract, args) + case ValidatorsMethod: + bz, err = p.Validators(ctx, method, contract, args) + case RedelegationMethod: + bz, err = p.Redelegation(ctx, method, contract, args) + case RedelegationsMethod: + bz, err = p.Redelegations(ctx, method, contract, args) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + // TODO: Should be called before calling keeper methods that modify the stateDB. + if err := p.AddJournalEntries(stateDB, snapshot); err != nil { + return nil, err + } + + return bz, nil + }) } // IsTransaction checks if the given method name corresponds to a transaction or query. From 3933be11afd4e23bc0aae557ab123bb38e68ff98 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Mon, 9 Jun 2025 21:31:47 +0900 Subject: [PATCH 07/19] chore: fix typo --- precompiles/common/precompile.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/precompiles/common/precompile.go b/precompiles/common/precompile.go index fdef9eb8d..62f6a1743 100644 --- a/precompiles/common/precompile.go +++ b/precompiles/common/precompile.go @@ -23,7 +23,7 @@ type Precompile struct { KvGasConfig storetypes.GasConfig TransientKVGasConfig storetypes.GasConfig address common.Address - balanceTracker *BalanceHandler + balanceHandler *BalanceHandler } // Operation is a type that defines if the precompile call @@ -250,8 +250,8 @@ func (p Precompile) standardCallData(contract *vm.Contract) (method *abi.Method, } func (p *Precompile) GetBalanceHandler() *BalanceHandler { - if p.balanceTracker == nil { - p.balanceTracker = NewBalanceHandler() + if p.balanceHandler == nil { + p.balanceHandler = NewBalanceHandler() } - return p.balanceTracker + return p.balanceHandler } From a62d50284b3664d8d4c7b568c9160bcf74059bd3 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Fri, 13 Jun 2025 18:07:05 +0900 Subject: [PATCH 08/19] chore: resolve conflict --- precompiles/bank/bank.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index a85b6a7bd..c3538204d 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -104,7 +104,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract bank query methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + ctx, _, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) if err != nil { return nil, err } @@ -134,9 +134,6 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { return nil, vm.ErrOutOfGas } - if err = p.AddJournalEntries(stateDB); err != nil { - return nil, err - } return bz, nil } From 0ccd71213a8ff7f11f70554b8d5d1952b3915d8b Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Sat, 14 Jun 2025 13:33:23 +0900 Subject: [PATCH 09/19] chore: fix lint --- precompiles/werc20/werc20.go | 1 - 1 file changed, 1 deletion(-) diff --git a/precompiles/werc20/werc20.go b/precompiles/werc20/werc20.go index b42e20693..607eefe59 100644 --- a/precompiles/werc20/werc20.go +++ b/precompiles/werc20/werc20.go @@ -148,7 +148,6 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ } return bz, nil - } // IsTransaction returns true if the given method name correspond to a From e744bd97edd3e188233884b51e7e6b3267d78841 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Thu, 26 Jun 2025 20:21:05 +0900 Subject: [PATCH 10/19] test(precompiles/common) add unit test cases --- precompiles/common/balance_handler_test.go | 143 +++++++++++++++++++++ testutil/statedb.go | 108 ++++++++++++++++ x/vm/statedb/mock_test.go | 117 ----------------- x/vm/statedb/statedb_test.go | 84 +++++++----- 4 files changed, 300 insertions(+), 152 deletions(-) create mode 100644 precompiles/common/balance_handler_test.go delete mode 100644 x/vm/statedb/mock_test.go diff --git a/precompiles/common/balance_handler_test.go b/precompiles/common/balance_handler_test.go new file mode 100644 index 000000000..359d6e521 --- /dev/null +++ b/precompiles/common/balance_handler_test.go @@ -0,0 +1,143 @@ +package common + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" + + testutil "github.com/cosmos/evm/testutil" + testconstants "github.com/cosmos/evm/testutil/constants" + "github.com/cosmos/evm/x/vm/statedb" + evmtypes "github.com/cosmos/evm/x/vm/types" + + storetypes "cosmossdk.io/store/types" + sdktestutil "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +func setupBalanceHandlerTest(t *testing.T) { + t.Helper() + + sdk.GetConfig().SetBech32PrefixForAccount(testconstants.ExampleBech32Prefix, "") + configurator := evmtypes.NewEVMConfigurator() + configurator.ResetTestConfig() + require.NoError(t, configurator.WithEVMCoinInfo(testconstants.ExampleChainCoinInfo[testconstants.ExampleChainID]).Configure()) +} + +func TestParseHexAddress(t *testing.T) { + setupBalanceHandlerTest(t) + + _, addrs, err := testutil.GeneratePrivKeyAddressPairs(1) + require.NoError(t, err) + accAddr := addrs[0] + + // valid address + ev := sdk.NewEvent("bank", sdk.NewAttribute(banktypes.AttributeKeySpender, accAddr.String())) + addr, err := parseHexAddress(ev, banktypes.AttributeKeySpender) + require.NoError(t, err) + require.Equal(t, common.Address(accAddr.Bytes()), addr) + + // missing attribute + ev = sdk.NewEvent("bank") + _, err = parseHexAddress(ev, banktypes.AttributeKeySpender) + require.Error(t, err) + + // invalid address + ev = sdk.NewEvent("bank", sdk.NewAttribute(banktypes.AttributeKeySpender, "invalid")) + _, err = parseHexAddress(ev, banktypes.AttributeKeySpender) + require.Error(t, err) +} + +func TestParseAmount(t *testing.T) { + setupBalanceHandlerTest(t) + + coinStr := sdk.NewCoins(sdk.NewInt64Coin(evmtypes.GetEVMCoinDenom(), 5)).String() + ev := sdk.NewEvent("bank", sdk.NewAttribute(sdk.AttributeKeyAmount, coinStr)) + amt, err := parseAmount(ev) + require.NoError(t, err) + require.True(t, amt.Eq(uint256.NewInt(5))) + + // missing amount + ev = sdk.NewEvent("bank") + _, err = parseAmount(ev) + require.Error(t, err) + + // invalid coins + ev = sdk.NewEvent("bank", sdk.NewAttribute(sdk.AttributeKeyAmount, "invalid")) + _, err = parseAmount(ev) + require.Error(t, err) +} + +func TestAfterBalanceChange(t *testing.T) { + setupBalanceHandlerTest(t) + + storeKey := storetypes.NewKVStoreKey("test") + tKey := storetypes.NewTransientStoreKey("test_t") + ctx := sdktestutil.DefaultContext(storeKey, tKey) + + stateDB := statedb.New(ctx, testutil.NewMockKeeper(), statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))) + + _, addrs, err := testutil.GeneratePrivKeyAddressPairs(2) + require.NoError(t, err) + spenderAcc := addrs[0] + receiverAcc := addrs[1] + spender := common.Address(spenderAcc.Bytes()) + receiver := common.Address(receiverAcc.Bytes()) + + // initial balance for spender + stateDB.AddBalance(spender, uint256.NewInt(5), tracing.BalanceChangeUnspecified) + + bh := NewBalanceHandler() + bh.BeforeBalanceChange(ctx) + + coins := sdk.NewCoins(sdk.NewInt64Coin(evmtypes.GetEVMCoinDenom(), 3)) + ctx.EventManager().EmitEvents(sdk.Events{ + banktypes.NewCoinSpentEvent(spenderAcc, coins), + banktypes.NewCoinReceivedEvent(receiverAcc, coins), + }) + + err = bh.AfterBalanceChange(ctx, stateDB) + require.NoError(t, err) + + require.Equal(t, "2", stateDB.GetBalance(spender).String()) + require.Equal(t, "3", stateDB.GetBalance(receiver).String()) +} + +func TestAfterBalanceChangeErrors(t *testing.T) { + setupBalanceHandlerTest(t) + + storeKey := storetypes.NewKVStoreKey("test") + tKey := storetypes.NewTransientStoreKey("test_t") + ctx := sdktestutil.DefaultContext(storeKey, tKey) + stateDB := statedb.New(ctx, testutil.NewMockKeeper(), statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))) + + _, addrs, err := testutil.GeneratePrivKeyAddressPairs(1) + require.NoError(t, err) + addr := addrs[0] + + bh := NewBalanceHandler() + bh.BeforeBalanceChange(ctx) + + // invalid address in event + coins := sdk.NewCoins(sdk.NewInt64Coin(evmtypes.GetEVMCoinDenom(), 1)) + ctx.EventManager().EmitEvent(banktypes.NewCoinSpentEvent(addr, coins)) + ctx.EventManager().Events()[len(ctx.EventManager().Events())-1].Attributes[0].Value = "invalid" + err = bh.AfterBalanceChange(ctx, stateDB) + require.Error(t, err) + + // reset events + ctx = ctx.WithEventManager(sdk.NewEventManager()) + bh.BeforeBalanceChange(ctx) + + // invalid amount + ev := sdk.NewEvent(banktypes.EventTypeCoinSpent, + sdk.NewAttribute(banktypes.AttributeKeySpender, addr.String()), + sdk.NewAttribute(sdk.AttributeKeyAmount, "invalid")) + ctx.EventManager().EmitEvent(ev) + err = bh.AfterBalanceChange(ctx, stateDB) + require.Error(t, err) +} diff --git a/testutil/statedb.go b/testutil/statedb.go index f248ae4d3..f88499855 100644 --- a/testutil/statedb.go +++ b/testutil/statedb.go @@ -1,15 +1,123 @@ package testutil import ( + "errors" + "maps" + "math/big" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" anteinterfaces "github.com/cosmos/evm/ante/interfaces" "github.com/cosmos/evm/x/vm/statedb" + "github.com/cosmos/evm/x/vm/types" sdk "github.com/cosmos/cosmos-sdk/types" ) +var ( + _ statedb.Keeper = &MockKeeper{} + ErrAddress common.Address = common.BigToAddress(big.NewInt(100)) + EmptyCodeHash = crypto.Keccak256(nil) +) + // NewStateDB returns a new StateDB for testing purposes. func NewStateDB(ctx sdk.Context, evmKeeper anteinterfaces.EVMKeeper) *statedb.StateDB { return statedb.New(ctx, evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))) } + +type MockAcount struct { + account statedb.Account + states statedb.Storage +} + +type MockKeeper struct { + accounts map[common.Address]MockAcount + codes map[common.Hash][]byte +} + +func NewMockKeeper() *MockKeeper { + return &MockKeeper{ + accounts: make(map[common.Address]MockAcount), + codes: make(map[common.Hash][]byte), + } +} + +func (k MockKeeper) GetAccount(_ sdk.Context, addr common.Address) *statedb.Account { + acct, ok := k.accounts[addr] + if !ok { + return nil + } + return &acct.account +} + +func (k MockKeeper) GetState(_ sdk.Context, addr common.Address, key common.Hash) common.Hash { + return k.accounts[addr].states[key] +} + +func (k MockKeeper) GetCode(_ sdk.Context, codeHash common.Hash) []byte { + return k.codes[codeHash] +} + +func (k MockKeeper) ForEachStorage(_ sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) { + if acct, ok := k.accounts[addr]; ok { + for k, v := range acct.states { + if !cb(k, v) { + return + } + } + } +} + +func (k MockKeeper) SetAccount(_ sdk.Context, addr common.Address, account statedb.Account) error { + if addr == ErrAddress { + return errors.New("mock db error") + } + acct, exists := k.accounts[addr] + if exists { + // update + acct.account = account + k.accounts[addr] = acct + } else { + k.accounts[addr] = MockAcount{account: account, states: make(statedb.Storage)} + } + return nil +} + +func (k MockKeeper) SetState(_ sdk.Context, addr common.Address, key common.Hash, value []byte) { + if acct, ok := k.accounts[addr]; ok { + acct.states[key] = common.BytesToHash(value) + } +} + +func (k MockKeeper) DeleteState(_ sdk.Context, addr common.Address, key common.Hash) { + if acct, ok := k.accounts[addr]; ok { + delete(acct.states, key) + } +} + +func (k MockKeeper) SetCode(_ sdk.Context, codeHash []byte, code []byte) { + k.codes[common.BytesToHash(codeHash)] = code +} + +func (k MockKeeper) DeleteCode(_ sdk.Context, codeHash []byte) { + delete(k.codes, common.BytesToHash(codeHash)) +} + +func (k MockKeeper) DeleteAccount(_ sdk.Context, addr common.Address) error { + if addr == ErrAddress { + return errors.New("mock db error") + } + old := k.accounts[addr] + delete(k.accounts, addr) + if !types.IsEmptyCodeHash(old.account.CodeHash) { + delete(k.codes, common.BytesToHash(old.account.CodeHash)) + } + return nil +} + +func (k MockKeeper) Clone() *MockKeeper { + accounts := maps.Clone(k.accounts) + codes := maps.Clone(k.codes) + return &MockKeeper{accounts, codes} +} diff --git a/x/vm/statedb/mock_test.go b/x/vm/statedb/mock_test.go deleted file mode 100644 index 130d771e6..000000000 --- a/x/vm/statedb/mock_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package statedb_test - -import ( - "errors" - "maps" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/cosmos/evm/x/vm/statedb" - "github.com/cosmos/evm/x/vm/types" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -var ( - _ statedb.Keeper = &MockKeeper{} - errAddress common.Address = common.BigToAddress(big.NewInt(100)) - emptyCodeHash = crypto.Keccak256(nil) -) - -type MockAcount struct { - account statedb.Account - states statedb.Storage -} - -type MockKeeper struct { - accounts map[common.Address]MockAcount - codes map[common.Hash][]byte -} - -func NewMockKeeper() *MockKeeper { - return &MockKeeper{ - accounts: make(map[common.Address]MockAcount), - codes: make(map[common.Hash][]byte), - } -} - -func (k MockKeeper) GetAccount(_ sdk.Context, addr common.Address) *statedb.Account { - acct, ok := k.accounts[addr] - if !ok { - return nil - } - return &acct.account -} - -func (k MockKeeper) GetState(_ sdk.Context, addr common.Address, key common.Hash) common.Hash { - return k.accounts[addr].states[key] -} - -func (k MockKeeper) GetCode(_ sdk.Context, codeHash common.Hash) []byte { - return k.codes[codeHash] -} - -func (k MockKeeper) ForEachStorage(_ sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) { - if acct, ok := k.accounts[addr]; ok { - for k, v := range acct.states { - if !cb(k, v) { - return - } - } - } -} - -func (k MockKeeper) SetAccount(_ sdk.Context, addr common.Address, account statedb.Account) error { - if addr == errAddress { - return errors.New("mock db error") - } - acct, exists := k.accounts[addr] - if exists { - // update - acct.account = account - k.accounts[addr] = acct - } else { - k.accounts[addr] = MockAcount{account: account, states: make(statedb.Storage)} - } - return nil -} - -func (k MockKeeper) SetState(_ sdk.Context, addr common.Address, key common.Hash, value []byte) { - if acct, ok := k.accounts[addr]; ok { - acct.states[key] = common.BytesToHash(value) - } -} - -func (k MockKeeper) DeleteState(_ sdk.Context, addr common.Address, key common.Hash) { - if acct, ok := k.accounts[addr]; ok { - delete(acct.states, key) - } -} - -func (k MockKeeper) SetCode(_ sdk.Context, codeHash []byte, code []byte) { - k.codes[common.BytesToHash(codeHash)] = code -} - -func (k MockKeeper) DeleteCode(_ sdk.Context, codeHash []byte) { - delete(k.codes, common.BytesToHash(codeHash)) -} - -func (k MockKeeper) DeleteAccount(_ sdk.Context, addr common.Address) error { - if addr == errAddress { - return errors.New("mock db error") - } - old := k.accounts[addr] - delete(k.accounts, addr) - if !types.IsEmptyCodeHash(old.account.CodeHash) { - delete(k.codes, common.BytesToHash(old.account.CodeHash)) - } - return nil -} - -func (k MockKeeper) Clone() *MockKeeper { - accounts := maps.Clone(k.accounts) - codes := maps.Clone(k.codes) - return &MockKeeper{accounts, codes} -} diff --git a/x/vm/statedb/statedb_test.go b/x/vm/statedb/statedb_test.go index ff3d1338e..af697e497 100644 --- a/x/vm/statedb/statedb_test.go +++ b/x/vm/statedb/statedb_test.go @@ -14,6 +14,7 @@ import ( "github.com/holiman/uint256" "github.com/stretchr/testify/suite" + "github.com/cosmos/evm/testutil" "github.com/cosmos/evm/x/vm/statedb" sdk "github.com/cosmos/cosmos-sdk/types" @@ -38,9 +39,9 @@ func (suite *StateDBTestSuite) TestAccount() { value2 := common.BigToHash(big.NewInt(4)) testCases := []struct { name string - malleate func(*statedb.StateDB) + malleate func(sdk.Context, *statedb.StateDB) }{ - {"non-exist account", func(db *statedb.StateDB) { + {"non-exist account", func(_ sdk.Context, db *statedb.StateDB) { suite.Require().Equal(false, db.Exist(address)) suite.Require().Equal(true, db.Empty(address)) suite.Require().Equal(common.U2560, db.GetBalance(address)) @@ -48,25 +49,25 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().Equal(common.Hash{}, db.GetCodeHash(address)) suite.Require().Equal(uint64(0), db.GetNonce(address)) }}, - {"empty account", func(db *statedb.StateDB) { + {"empty account", func(ctx sdk.Context, db *statedb.StateDB) { db.CreateAccount(address) suite.Require().NoError(db.Commit()) - keeper := db.Keeper().(*MockKeeper) - acct := keeper.accounts[address] - suite.Require().Equal(statedb.NewEmptyAccount(), &acct.account) - suite.Require().Empty(acct.states) - suite.Require().False(acct.account.IsContract()) + keeper := db.Keeper().(*testutil.MockKeeper) + acct := keeper.GetAccount(ctx, address) + suite.Require().Equal(statedb.NewEmptyAccount(), acct) + suite.Require().Empty(acct.Balance) + suite.Require().False(acct.IsContract()) db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) suite.Require().Equal(true, db.Exist(address)) suite.Require().Equal(true, db.Empty(address)) suite.Require().Equal(common.U2560, db.GetBalance(address)) suite.Require().Equal([]byte(nil), db.GetCode(address)) - suite.Require().Equal(common.BytesToHash(emptyCodeHash), db.GetCodeHash(address)) + suite.Require().Equal(common.BytesToHash(testutil.EmptyCodeHash), db.GetCodeHash(address)) suite.Require().Equal(uint64(0), db.GetNonce(address)) }}, - {"suicide", func(db *statedb.StateDB) { + {"suicide", func(ctx sdk.Context, db *statedb.StateDB) { // non-exist account. db.SelfDestruct(address) suite.Require().False(db.HasSelfDestructed(address)) @@ -99,22 +100,27 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().False(db.Exist(address)) // and cleared in keeper too - keeper := db.Keeper().(*MockKeeper) - suite.Require().Empty(keeper.accounts) - suite.Require().Empty(keeper.codes) + keeper := db.Keeper().(*testutil.MockKeeper) + keeper.ForEachStorage(ctx, address, func(key, value common.Hash) bool { + if len(value) != 0 { + return false + } + return true + }) }}, } for _, tc := range testCases { suite.Run(tc.name, func() { - keeper := NewMockKeeper() + ctx := sdk.Context{} + keeper := testutil.NewMockKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) - tc.malleate(db) + tc.malleate(ctx, db) }) } } func (suite *StateDBTestSuite) TestAccountOverride() { - keeper := NewMockKeeper() + keeper := testutil.NewMockKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) // test balance carry over when overwritten amount := uint256.NewInt(1) @@ -138,16 +144,16 @@ func (suite *StateDBTestSuite) TestDBError() { malleate func(vm.StateDB) }{ {"set account", func(db vm.StateDB) { - db.SetNonce(errAddress, 1, tracing.NonceChangeUnspecified) + db.SetNonce(testutil.ErrAddress, 1, tracing.NonceChangeUnspecified) }}, {"delete account", func(db vm.StateDB) { - db.SetNonce(errAddress, 1, tracing.NonceChangeUnspecified) - db.SelfDestruct(errAddress) - suite.Require().True(db.HasSelfDestructed(errAddress)) + db.SetNonce(testutil.ErrAddress, 1, tracing.NonceChangeUnspecified) + db.SelfDestruct(testutil.ErrAddress) + suite.Require().True(db.HasSelfDestructed(testutil.ErrAddress)) }}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), emptyTxConfig) tc.malleate(db) suite.Require().Error(db.Commit()) } @@ -179,7 +185,8 @@ func (suite *StateDBTestSuite) TestBalance() { for _, tc := range testCases { suite.Run(tc.name, func() { - keeper := NewMockKeeper() + ctx := sdk.Context{} + keeper := testutil.NewMockKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(db) @@ -187,7 +194,7 @@ func (suite *StateDBTestSuite) TestBalance() { suite.Require().Equal(tc.expBalance, db.GetBalance(address)) suite.Require().NoError(db.Commit()) // check committed balance too - suite.Require().Equal(tc.expBalance, keeper.accounts[address].account.Balance) + suite.Require().Equal(tc.expBalance, keeper.GetAccount(ctx, address).Balance) }) } } @@ -233,13 +240,16 @@ func (suite *StateDBTestSuite) TestState() { for _, tc := range testCases { suite.Run(tc.name, func() { - keeper := NewMockKeeper() + ctx := sdk.Context{} + keeper := testutil.NewMockKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(db) suite.Require().NoError(db.Commit()) // check committed states in keeper - suite.Require().Equal(tc.expStates, keeper.accounts[address].states) + for _, key := range tc.expStates.SortedKeys() { + suite.Require().Equal(tc.expStates[key], keeper.GetState(ctx, address, key)) + } // check ForEachStorage db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) @@ -266,7 +276,7 @@ func (suite *StateDBTestSuite) TestCode() { {"non-exist account", func(vm.StateDB) {}, nil, common.Hash{}}, {"empty account", func(db vm.StateDB) { db.CreateAccount(address) - }, nil, common.BytesToHash(emptyCodeHash)}, + }, nil, common.BytesToHash(testutil.EmptyCodeHash)}, {"set code", func(db vm.StateDB) { db.SetCode(address, code) }, code, codeHash}, @@ -274,7 +284,7 @@ func (suite *StateDBTestSuite) TestCode() { for _, tc := range testCases { suite.Run(tc.name, func() { - keeper := NewMockKeeper() + keeper := testutil.NewMockKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(db) @@ -341,7 +351,7 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { for _, tc := range testCases { suite.Run(tc.name, func() { ctx := sdk.Context{} - keeper := NewMockKeeper() + keeper := testutil.NewMockKeeper() { // do some arbitrary changes to the storage @@ -379,7 +389,7 @@ func (suite *StateDBTestSuite) TestNestedSnapshot() { value1 := common.BigToHash(big.NewInt(1)) value2 := common.BigToHash(big.NewInt(2)) - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), emptyTxConfig) rev1 := db.Snapshot() db.SetState(address, key, value1) @@ -396,7 +406,7 @@ func (suite *StateDBTestSuite) TestNestedSnapshot() { } func (suite *StateDBTestSuite) TestInvalidSnapshotId() { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), emptyTxConfig) suite.Require().Panics(func() { db.RevertToSnapshot(1) }) @@ -486,7 +496,7 @@ func (suite *StateDBTestSuite) TestAccessList() { } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), emptyTxConfig) tc.malleate(db) } } @@ -499,7 +509,7 @@ func (suite *StateDBTestSuite) TestLog() { txHash, 1, 1, ) - db := statedb.New(sdk.Context{}, NewMockKeeper(), txConfig) + db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), txConfig) data := []byte("hello world") db.AddLog(ðtypes.Log{ Address: address, @@ -551,7 +561,7 @@ func (suite *StateDBTestSuite) TestRefund() { }, 0, true}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), emptyTxConfig) if !tc.expPanic { tc.malleate(db) suite.Require().Equal(tc.expRefund, db.GetRefund()) @@ -564,12 +574,14 @@ func (suite *StateDBTestSuite) TestRefund() { } func (suite *StateDBTestSuite) TestIterateStorage() { + ctx := sdk.Context{} + key1 := common.BigToHash(big.NewInt(1)) value1 := common.BigToHash(big.NewInt(2)) key2 := common.BigToHash(big.NewInt(3)) value2 := common.BigToHash(big.NewInt(4)) - keeper := NewMockKeeper() + keeper := testutil.NewMockKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) db.SetState(address, key1, value1) db.SetState(address, key2, value2) @@ -581,7 +593,9 @@ func (suite *StateDBTestSuite) TestIterateStorage() { storage := CollectContractStorage(db) suite.Require().Equal(2, len(storage)) - suite.Require().Equal(keeper.accounts[address].states, storage) + for _, key := range storage.SortedKeys() { + suite.Require().Equal(keeper.GetState(ctx, address, key), storage[key]) + } // break early iteration storage = make(statedb.Storage) From 5239be2d8078d4d5d57c587d8fe8c0c30b2829c0 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Thu, 26 Jun 2025 21:39:46 +0900 Subject: [PATCH 11/19] chore: fix lint --- precompiles/common/balance_handler_test.go | 1 + x/vm/statedb/statedb_test.go | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/precompiles/common/balance_handler_test.go b/precompiles/common/balance_handler_test.go index 359d6e521..fdbe09527 100644 --- a/precompiles/common/balance_handler_test.go +++ b/precompiles/common/balance_handler_test.go @@ -14,6 +14,7 @@ import ( evmtypes "github.com/cosmos/evm/x/vm/types" storetypes "cosmossdk.io/store/types" + sdktestutil "github.com/cosmos/cosmos-sdk/testutil" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" diff --git a/x/vm/statedb/statedb_test.go b/x/vm/statedb/statedb_test.go index af697e497..6bcfe2dcf 100644 --- a/x/vm/statedb/statedb_test.go +++ b/x/vm/statedb/statedb_test.go @@ -102,10 +102,7 @@ func (suite *StateDBTestSuite) TestAccount() { // and cleared in keeper too keeper := db.Keeper().(*testutil.MockKeeper) keeper.ForEachStorage(ctx, address, func(key, value common.Hash) bool { - if len(value) != 0 { - return false - } - return true + return len(value) == 0 }) }}, } From 62aa9f1a8ea32161dff7eab6611444d3527a5886 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Fri, 27 Jun 2025 12:34:14 +0900 Subject: [PATCH 12/19] fix(test): precompile test case that intermittently fails --- .../integration/precompiles/distribution/test_integration.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/precompiles/distribution/test_integration.go b/tests/integration/precompiles/distribution/test_integration.go index 732840875..66b350899 100644 --- a/tests/integration/precompiles/distribution/test_integration.go +++ b/tests/integration/precompiles/distribution/test_integration.go @@ -2274,7 +2274,7 @@ func TestPrecompileIntegrationTestSuite(t *testing.T, create network.CreateEvmAp // set gas such that the internal keeper function called by the precompile fails out mid-execution txArgs.GasLimit = 80_000 - _, _, err = s.factory.CallContractAndCheckLogs( + _, txRes, err := s.factory.CallContractAndCheckLogs( s.keyring.GetPrivKey(0), txArgs, callArgs, @@ -2286,7 +2286,7 @@ func TestPrecompileIntegrationTestSuite(t *testing.T, create network.CreateEvmAp balRes, err := s.grpcHandler.GetBalanceFromBank(s.keyring.GetAccAddr(0), s.bondDenom) Expect(err).To(BeNil()) finalBalance := balRes.Balance - expectedGasCost := math.NewInt(79_416_000_000_000) + expectedGasCost := math.NewIntFromUint64(txRes.GasUsed).Mul(math.NewIntFromBigInt(txArgs.GasPrice)) Expect(finalBalance.Amount.Equal(initialBalance.Amount.Sub(expectedGasCost))).To(BeTrue(), "expected final balance must be initial balance minus any gas spent") res, err = s.grpcHandler.GetDelegationTotalRewards(s.keyring.GetAccAddr(0).String()) From 2ce685a0da2d1f9f947fead1906559435d2f5476 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Mon, 30 Jun 2025 19:58:41 +0900 Subject: [PATCH 13/19] refactor: move mock evm keeper to x/vm/types/mocks --- precompiles/common/balance_handler_test.go | 5 +- testutil/statedb.go | 108 ------------------- x/vm/statedb/statedb_test.go | 44 ++++---- x/vm/types/mocks/EVMKeeper.go | 117 +++++++++++++++++++++ 4 files changed, 142 insertions(+), 132 deletions(-) create mode 100644 x/vm/types/mocks/EVMKeeper.go diff --git a/precompiles/common/balance_handler_test.go b/precompiles/common/balance_handler_test.go index fdbe09527..efc74e52f 100644 --- a/precompiles/common/balance_handler_test.go +++ b/precompiles/common/balance_handler_test.go @@ -12,6 +12,7 @@ import ( testconstants "github.com/cosmos/evm/testutil/constants" "github.com/cosmos/evm/x/vm/statedb" evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/cosmos/evm/x/vm/types/mocks" storetypes "cosmossdk.io/store/types" @@ -80,7 +81,7 @@ func TestAfterBalanceChange(t *testing.T) { tKey := storetypes.NewTransientStoreKey("test_t") ctx := sdktestutil.DefaultContext(storeKey, tKey) - stateDB := statedb.New(ctx, testutil.NewMockKeeper(), statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))) + stateDB := statedb.New(ctx, mocks.NewEVMKeeper(), statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))) _, addrs, err := testutil.GeneratePrivKeyAddressPairs(2) require.NoError(t, err) @@ -114,7 +115,7 @@ func TestAfterBalanceChangeErrors(t *testing.T) { storeKey := storetypes.NewKVStoreKey("test") tKey := storetypes.NewTransientStoreKey("test_t") ctx := sdktestutil.DefaultContext(storeKey, tKey) - stateDB := statedb.New(ctx, testutil.NewMockKeeper(), statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))) + stateDB := statedb.New(ctx, mocks.NewEVMKeeper(), statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))) _, addrs, err := testutil.GeneratePrivKeyAddressPairs(1) require.NoError(t, err) diff --git a/testutil/statedb.go b/testutil/statedb.go index f88499855..f248ae4d3 100644 --- a/testutil/statedb.go +++ b/testutil/statedb.go @@ -1,123 +1,15 @@ package testutil import ( - "errors" - "maps" - "math/big" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" anteinterfaces "github.com/cosmos/evm/ante/interfaces" "github.com/cosmos/evm/x/vm/statedb" - "github.com/cosmos/evm/x/vm/types" sdk "github.com/cosmos/cosmos-sdk/types" ) -var ( - _ statedb.Keeper = &MockKeeper{} - ErrAddress common.Address = common.BigToAddress(big.NewInt(100)) - EmptyCodeHash = crypto.Keccak256(nil) -) - // NewStateDB returns a new StateDB for testing purposes. func NewStateDB(ctx sdk.Context, evmKeeper anteinterfaces.EVMKeeper) *statedb.StateDB { return statedb.New(ctx, evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))) } - -type MockAcount struct { - account statedb.Account - states statedb.Storage -} - -type MockKeeper struct { - accounts map[common.Address]MockAcount - codes map[common.Hash][]byte -} - -func NewMockKeeper() *MockKeeper { - return &MockKeeper{ - accounts: make(map[common.Address]MockAcount), - codes: make(map[common.Hash][]byte), - } -} - -func (k MockKeeper) GetAccount(_ sdk.Context, addr common.Address) *statedb.Account { - acct, ok := k.accounts[addr] - if !ok { - return nil - } - return &acct.account -} - -func (k MockKeeper) GetState(_ sdk.Context, addr common.Address, key common.Hash) common.Hash { - return k.accounts[addr].states[key] -} - -func (k MockKeeper) GetCode(_ sdk.Context, codeHash common.Hash) []byte { - return k.codes[codeHash] -} - -func (k MockKeeper) ForEachStorage(_ sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) { - if acct, ok := k.accounts[addr]; ok { - for k, v := range acct.states { - if !cb(k, v) { - return - } - } - } -} - -func (k MockKeeper) SetAccount(_ sdk.Context, addr common.Address, account statedb.Account) error { - if addr == ErrAddress { - return errors.New("mock db error") - } - acct, exists := k.accounts[addr] - if exists { - // update - acct.account = account - k.accounts[addr] = acct - } else { - k.accounts[addr] = MockAcount{account: account, states: make(statedb.Storage)} - } - return nil -} - -func (k MockKeeper) SetState(_ sdk.Context, addr common.Address, key common.Hash, value []byte) { - if acct, ok := k.accounts[addr]; ok { - acct.states[key] = common.BytesToHash(value) - } -} - -func (k MockKeeper) DeleteState(_ sdk.Context, addr common.Address, key common.Hash) { - if acct, ok := k.accounts[addr]; ok { - delete(acct.states, key) - } -} - -func (k MockKeeper) SetCode(_ sdk.Context, codeHash []byte, code []byte) { - k.codes[common.BytesToHash(codeHash)] = code -} - -func (k MockKeeper) DeleteCode(_ sdk.Context, codeHash []byte) { - delete(k.codes, common.BytesToHash(codeHash)) -} - -func (k MockKeeper) DeleteAccount(_ sdk.Context, addr common.Address) error { - if addr == ErrAddress { - return errors.New("mock db error") - } - old := k.accounts[addr] - delete(k.accounts, addr) - if !types.IsEmptyCodeHash(old.account.CodeHash) { - delete(k.codes, common.BytesToHash(old.account.CodeHash)) - } - return nil -} - -func (k MockKeeper) Clone() *MockKeeper { - accounts := maps.Clone(k.accounts) - codes := maps.Clone(k.codes) - return &MockKeeper{accounts, codes} -} diff --git a/x/vm/statedb/statedb_test.go b/x/vm/statedb/statedb_test.go index 6bcfe2dcf..7d8cd220c 100644 --- a/x/vm/statedb/statedb_test.go +++ b/x/vm/statedb/statedb_test.go @@ -14,8 +14,8 @@ import ( "github.com/holiman/uint256" "github.com/stretchr/testify/suite" - "github.com/cosmos/evm/testutil" "github.com/cosmos/evm/x/vm/statedb" + "github.com/cosmos/evm/x/vm/types/mocks" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -53,7 +53,7 @@ func (suite *StateDBTestSuite) TestAccount() { db.CreateAccount(address) suite.Require().NoError(db.Commit()) - keeper := db.Keeper().(*testutil.MockKeeper) + keeper := db.Keeper().(*mocks.EVMKeeper) acct := keeper.GetAccount(ctx, address) suite.Require().Equal(statedb.NewEmptyAccount(), acct) suite.Require().Empty(acct.Balance) @@ -64,7 +64,7 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().Equal(true, db.Empty(address)) suite.Require().Equal(common.U2560, db.GetBalance(address)) suite.Require().Equal([]byte(nil), db.GetCode(address)) - suite.Require().Equal(common.BytesToHash(testutil.EmptyCodeHash), db.GetCodeHash(address)) + suite.Require().Equal(common.BytesToHash(mocks.EmptyCodeHash), db.GetCodeHash(address)) suite.Require().Equal(uint64(0), db.GetNonce(address)) }}, {"suicide", func(ctx sdk.Context, db *statedb.StateDB) { @@ -100,7 +100,7 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().False(db.Exist(address)) // and cleared in keeper too - keeper := db.Keeper().(*testutil.MockKeeper) + keeper := db.Keeper().(*mocks.EVMKeeper) keeper.ForEachStorage(ctx, address, func(key, value common.Hash) bool { return len(value) == 0 }) @@ -109,7 +109,7 @@ func (suite *StateDBTestSuite) TestAccount() { for _, tc := range testCases { suite.Run(tc.name, func() { ctx := sdk.Context{} - keeper := testutil.NewMockKeeper() + keeper := mocks.NewEVMKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(ctx, db) }) @@ -117,7 +117,7 @@ func (suite *StateDBTestSuite) TestAccount() { } func (suite *StateDBTestSuite) TestAccountOverride() { - keeper := testutil.NewMockKeeper() + keeper := mocks.NewEVMKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) // test balance carry over when overwritten amount := uint256.NewInt(1) @@ -141,16 +141,16 @@ func (suite *StateDBTestSuite) TestDBError() { malleate func(vm.StateDB) }{ {"set account", func(db vm.StateDB) { - db.SetNonce(testutil.ErrAddress, 1, tracing.NonceChangeUnspecified) + db.SetNonce(mocks.ErrAddress, 1, tracing.NonceChangeUnspecified) }}, {"delete account", func(db vm.StateDB) { - db.SetNonce(testutil.ErrAddress, 1, tracing.NonceChangeUnspecified) - db.SelfDestruct(testutil.ErrAddress) - suite.Require().True(db.HasSelfDestructed(testutil.ErrAddress)) + db.SetNonce(mocks.ErrAddress, 1, tracing.NonceChangeUnspecified) + db.SelfDestruct(mocks.ErrAddress) + suite.Require().True(db.HasSelfDestructed(mocks.ErrAddress)) }}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, mocks.NewEVMKeeper(), emptyTxConfig) tc.malleate(db) suite.Require().Error(db.Commit()) } @@ -183,7 +183,7 @@ func (suite *StateDBTestSuite) TestBalance() { for _, tc := range testCases { suite.Run(tc.name, func() { ctx := sdk.Context{} - keeper := testutil.NewMockKeeper() + keeper := mocks.NewEVMKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(db) @@ -238,7 +238,7 @@ func (suite *StateDBTestSuite) TestState() { for _, tc := range testCases { suite.Run(tc.name, func() { ctx := sdk.Context{} - keeper := testutil.NewMockKeeper() + keeper := mocks.NewEVMKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(db) suite.Require().NoError(db.Commit()) @@ -273,7 +273,7 @@ func (suite *StateDBTestSuite) TestCode() { {"non-exist account", func(vm.StateDB) {}, nil, common.Hash{}}, {"empty account", func(db vm.StateDB) { db.CreateAccount(address) - }, nil, common.BytesToHash(testutil.EmptyCodeHash)}, + }, nil, common.BytesToHash(mocks.EmptyCodeHash)}, {"set code", func(db vm.StateDB) { db.SetCode(address, code) }, code, codeHash}, @@ -281,7 +281,7 @@ func (suite *StateDBTestSuite) TestCode() { for _, tc := range testCases { suite.Run(tc.name, func() { - keeper := testutil.NewMockKeeper() + keeper := mocks.NewEVMKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) tc.malleate(db) @@ -348,7 +348,7 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { for _, tc := range testCases { suite.Run(tc.name, func() { ctx := sdk.Context{} - keeper := testutil.NewMockKeeper() + keeper := mocks.NewEVMKeeper() { // do some arbitrary changes to the storage @@ -386,7 +386,7 @@ func (suite *StateDBTestSuite) TestNestedSnapshot() { value1 := common.BigToHash(big.NewInt(1)) value2 := common.BigToHash(big.NewInt(2)) - db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, mocks.NewEVMKeeper(), emptyTxConfig) rev1 := db.Snapshot() db.SetState(address, key, value1) @@ -403,7 +403,7 @@ func (suite *StateDBTestSuite) TestNestedSnapshot() { } func (suite *StateDBTestSuite) TestInvalidSnapshotId() { - db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, mocks.NewEVMKeeper(), emptyTxConfig) suite.Require().Panics(func() { db.RevertToSnapshot(1) }) @@ -493,7 +493,7 @@ func (suite *StateDBTestSuite) TestAccessList() { } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, mocks.NewEVMKeeper(), emptyTxConfig) tc.malleate(db) } } @@ -506,7 +506,7 @@ func (suite *StateDBTestSuite) TestLog() { txHash, 1, 1, ) - db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), txConfig) + db := statedb.New(sdk.Context{}, mocks.NewEVMKeeper(), txConfig) data := []byte("hello world") db.AddLog(ðtypes.Log{ Address: address, @@ -558,7 +558,7 @@ func (suite *StateDBTestSuite) TestRefund() { }, 0, true}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, testutil.NewMockKeeper(), emptyTxConfig) + db := statedb.New(sdk.Context{}, mocks.NewEVMKeeper(), emptyTxConfig) if !tc.expPanic { tc.malleate(db) suite.Require().Equal(tc.expRefund, db.GetRefund()) @@ -578,7 +578,7 @@ func (suite *StateDBTestSuite) TestIterateStorage() { key2 := common.BigToHash(big.NewInt(3)) value2 := common.BigToHash(big.NewInt(4)) - keeper := testutil.NewMockKeeper() + keeper := mocks.NewEVMKeeper() db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) db.SetState(address, key1, value1) db.SetState(address, key2, value2) diff --git a/x/vm/types/mocks/EVMKeeper.go b/x/vm/types/mocks/EVMKeeper.go new file mode 100644 index 000000000..0faef93ec --- /dev/null +++ b/x/vm/types/mocks/EVMKeeper.go @@ -0,0 +1,117 @@ +package mocks + +import ( + "errors" + "maps" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/cosmos/evm/x/vm/statedb" + "github.com/cosmos/evm/x/vm/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + _ statedb.Keeper = &EVMKeeper{} + ErrAddress common.Address = common.BigToAddress(big.NewInt(100)) + EmptyCodeHash = crypto.Keccak256(nil) +) + +type Account struct { + account statedb.Account + states statedb.Storage +} + +type EVMKeeper struct { + accounts map[common.Address]Account + codes map[common.Hash][]byte +} + +func NewEVMKeeper() *EVMKeeper { + return &EVMKeeper{ + accounts: make(map[common.Address]Account), + codes: make(map[common.Hash][]byte), + } +} + +func (k EVMKeeper) GetAccount(_ sdk.Context, addr common.Address) *statedb.Account { + acct, ok := k.accounts[addr] + if !ok { + return nil + } + return &acct.account +} + +func (k EVMKeeper) GetState(_ sdk.Context, addr common.Address, key common.Hash) common.Hash { + return k.accounts[addr].states[key] +} + +func (k EVMKeeper) GetCode(_ sdk.Context, codeHash common.Hash) []byte { + return k.codes[codeHash] +} + +func (k EVMKeeper) ForEachStorage(_ sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) { + if acct, ok := k.accounts[addr]; ok { + for k, v := range acct.states { + if !cb(k, v) { + return + } + } + } +} + +func (k EVMKeeper) SetAccount(_ sdk.Context, addr common.Address, account statedb.Account) error { + if addr == ErrAddress { + return errors.New("mock db error") + } + acct, exists := k.accounts[addr] + if exists { + // update + acct.account = account + k.accounts[addr] = acct + } else { + k.accounts[addr] = Account{account: account, states: make(statedb.Storage)} + } + return nil +} + +func (k EVMKeeper) SetState(_ sdk.Context, addr common.Address, key common.Hash, value []byte) { + if acct, ok := k.accounts[addr]; ok { + acct.states[key] = common.BytesToHash(value) + } +} + +func (k EVMKeeper) DeleteState(_ sdk.Context, addr common.Address, key common.Hash) { + if acct, ok := k.accounts[addr]; ok { + delete(acct.states, key) + } +} + +func (k EVMKeeper) SetCode(_ sdk.Context, codeHash []byte, code []byte) { + k.codes[common.BytesToHash(codeHash)] = code +} + +func (k EVMKeeper) DeleteCode(_ sdk.Context, codeHash []byte) { + delete(k.codes, common.BytesToHash(codeHash)) +} + +func (k EVMKeeper) DeleteAccount(_ sdk.Context, addr common.Address) error { + if addr == ErrAddress { + return errors.New("mock db error") + } + old := k.accounts[addr] + delete(k.accounts, addr) + if !types.IsEmptyCodeHash(old.account.CodeHash) { + delete(k.codes, common.BytesToHash(old.account.CodeHash)) + } + return nil +} + +func (k EVMKeeper) Clone() *EVMKeeper { + accounts := maps.Clone(k.accounts) + codes := maps.Clone(k.codes) + return &EVMKeeper{accounts, codes} +} From ea887e0753fe4273212f89cd53b7bd0ec3b32dd3 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Thu, 3 Jul 2025 11:29:47 +0900 Subject: [PATCH 14/19] chore: add KVStoreKeys() method to mock evmKeeper --- x/vm/types/mocks/EVMKeeper.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/x/vm/types/mocks/EVMKeeper.go b/x/vm/types/mocks/EVMKeeper.go index 0faef93ec..b7f6c4d24 100644 --- a/x/vm/types/mocks/EVMKeeper.go +++ b/x/vm/types/mocks/EVMKeeper.go @@ -11,6 +11,8 @@ import ( "github.com/cosmos/evm/x/vm/statedb" "github.com/cosmos/evm/x/vm/types" + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -26,14 +28,16 @@ type Account struct { } type EVMKeeper struct { - accounts map[common.Address]Account - codes map[common.Hash][]byte + accounts map[common.Address]Account + codes map[common.Hash][]byte + storeKeys map[string]*storetypes.KVStoreKey } func NewEVMKeeper() *EVMKeeper { return &EVMKeeper{ - accounts: make(map[common.Address]Account), - codes: make(map[common.Hash][]byte), + accounts: make(map[common.Address]Account), + codes: make(map[common.Hash][]byte), + storeKeys: make(map[string]*storetypes.KVStoreKey), } } @@ -113,5 +117,10 @@ func (k EVMKeeper) DeleteAccount(_ sdk.Context, addr common.Address) error { func (k EVMKeeper) Clone() *EVMKeeper { accounts := maps.Clone(k.accounts) codes := maps.Clone(k.codes) - return &EVMKeeper{accounts, codes} + storeKeys := maps.Clone(k.storeKeys) + return &EVMKeeper{accounts, codes, storeKeys} +} + +func (k EVMKeeper) KVStoreKeys() map[string]*storetypes.KVStoreKey { + return k.storeKeys } From 7c5ff7b69dd1a8c042a8dec8cc53f013219725fe Mon Sep 17 00:00:00 2001 From: zsystm Date: Thu, 3 Jul 2025 16:47:52 +0900 Subject: [PATCH 15/19] refactoring balance handling --- precompiles/common/precompile.go | 38 ++++++++ precompiles/distribution/distribution.go | 110 +++++++++-------------- precompiles/erc20/erc20.go | 40 ++------- precompiles/evidence/evidence.go | 62 ++++--------- precompiles/gov/gov.go | 109 +++++++++------------- precompiles/ics20/ics20.go | 67 +++++--------- precompiles/slashing/slashing.go | 62 ++++--------- precompiles/staking/staking.go | 95 ++++++++------------ precompiles/werc20/werc20.go | 62 ++++--------- 9 files changed, 247 insertions(+), 398 deletions(-) diff --git a/precompiles/common/precompile.go b/precompiles/common/precompile.go index a78ecfbe8..e188dae8e 100644 --- a/precompiles/common/precompile.go +++ b/precompiles/common/precompile.go @@ -231,3 +231,41 @@ func (p *Precompile) GetBalanceHandler() *BalanceHandler { } return p.balanceHandler } + +// ExecuteWithBalanceHandling wraps the execution of a precompile method with +// the necessary balance handling and gas accounting logic. Precompile +// implementations can use this helper to avoid repetitive boilerplate in their +// `Run` methods. +func (p Precompile) ExecuteWithBalanceHandling( + evm *vm.EVM, + contract *vm.Contract, + readOnly bool, + isTransaction func(*abi.Method) bool, + handle func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error), +) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, isTransaction) + if err != nil { + return nil, err + } + + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + defer HandleGasError(ctx, contract, initialGas, &err)() + + bz, err = handle(ctx, contract, stateDB, method, args) + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + return bz, nil +} diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index a9bb42faa..ee81249ec 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -3,10 +3,11 @@ package distribution import ( "embed" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -84,71 +85,48 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract distribution methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) - if err != nil { - return nil, err - } - - // Start the balance change handler before executing the precompile. - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - // This handles any out of gas errors that may occur during the execution of a precompile tx or query. - // It avoids panics and returns the out of gas error so the EVM can continue gracefully. - defer cmn.HandleGasError(ctx, contract, initialGas, &err)() - - switch method.Name { - // Custom transactions - case ClaimRewardsMethod: - bz, err = p.ClaimRewards(ctx, contract, stateDB, method, args) - // Distribution transactions - case SetWithdrawAddressMethod: - bz, err = p.SetWithdrawAddress(ctx, contract, stateDB, method, args) - case WithdrawDelegatorRewardMethod: - bz, err = p.WithdrawDelegatorReward(ctx, contract, stateDB, method, args) - case WithdrawValidatorCommissionMethod: - bz, err = p.WithdrawValidatorCommission(ctx, contract, stateDB, method, args) - case FundCommunityPoolMethod: - bz, err = p.FundCommunityPool(ctx, contract, stateDB, method, args) - case DepositValidatorRewardsPoolMethod: - bz, err = p.DepositValidatorRewardsPool(ctx, contract, stateDB, method, args) - // Distribution queries - case ValidatorDistributionInfoMethod: - bz, err = p.ValidatorDistributionInfo(ctx, contract, method, args) - case ValidatorOutstandingRewardsMethod: - bz, err = p.ValidatorOutstandingRewards(ctx, contract, method, args) - case ValidatorCommissionMethod: - bz, err = p.ValidatorCommission(ctx, contract, method, args) - case ValidatorSlashesMethod: - bz, err = p.ValidatorSlashes(ctx, contract, method, args) - case DelegationRewardsMethod: - bz, err = p.DelegationRewards(ctx, contract, method, args) - case DelegationTotalRewardsMethod: - bz, err = p.DelegationTotalRewards(ctx, contract, method, args) - case DelegatorValidatorsMethod: - bz, err = p.DelegatorValidators(ctx, contract, method, args) - case DelegatorWithdrawAddressMethod: - bz, err = p.DelegatorWithdrawAddress(ctx, contract, method, args) - case CommunityPoolMethod: - bz, err = p.CommunityPool(ctx, contract, method, args) - } - - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - // Process the native balance changes after the method execution. - if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { - return nil, err - } - - return bz, nil +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // Custom transactions + case ClaimRewardsMethod: + return p.ClaimRewards(ctx, contract, stateDB, method, args) + // Distribution transactions + case SetWithdrawAddressMethod: + return p.SetWithdrawAddress(ctx, contract, stateDB, method, args) + case WithdrawDelegatorRewardMethod: + return p.WithdrawDelegatorReward(ctx, contract, stateDB, method, args) + case WithdrawValidatorCommissionMethod: + return p.WithdrawValidatorCommission(ctx, contract, stateDB, method, args) + case FundCommunityPoolMethod: + return p.FundCommunityPool(ctx, contract, stateDB, method, args) + case DepositValidatorRewardsPoolMethod: + return p.DepositValidatorRewardsPool(ctx, contract, stateDB, method, args) + // Distribution queries + case ValidatorDistributionInfoMethod: + return p.ValidatorDistributionInfo(ctx, contract, method, args) + case ValidatorOutstandingRewardsMethod: + return p.ValidatorOutstandingRewards(ctx, contract, method, args) + case ValidatorCommissionMethod: + return p.ValidatorCommission(ctx, contract, method, args) + case ValidatorSlashesMethod: + return p.ValidatorSlashes(ctx, contract, method, args) + case DelegationRewardsMethod: + return p.DelegationRewards(ctx, contract, method, args) + case DelegationTotalRewardsMethod: + return p.DelegationTotalRewards(ctx, contract, method, args) + case DelegatorValidatorsMethod: + return p.DelegatorValidators(ctx, contract, method, args) + case DelegatorWithdrawAddressMethod: + return p.DelegatorWithdrawAddress(ctx, contract, method, args) + case CommunityPoolMethod: + return p.CommunityPool(ctx, contract, method, args) + } + return nil, nil + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/erc20/erc20.go b/precompiles/erc20/erc20.go index cc3f9176a..b8ccf1986 100644 --- a/precompiles/erc20/erc20.go +++ b/precompiles/erc20/erc20.go @@ -3,9 +3,9 @@ package erc20 import ( "embed" "fmt" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -135,7 +135,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract ERC-20 methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { // ERC20 precompiles cannot receive funds because they are not managed by an // EOA and will not be possible to recover funds sent to an instance of // them.This check is a safety measure because currently funds cannot be @@ -144,36 +144,12 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ return nil, fmt.Errorf(ErrCannotReceiveFunds, contract.Value().String()) } - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) - if err != nil { - return nil, err - } - - // Start the balance change handler before executing the precompile. - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - // This handles any out of gas errors that may occur during the execution of a precompile tx or query. - // It avoids panics and returns the out of gas error so the EVM can continue gracefully. - defer cmn.HandleGasError(ctx, contract, initialGas, &err)() - - bz, err = p.HandleMethod(ctx, contract, stateDB, method, args) - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - // Process the native balance changes after the method execution. - err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB) - if err != nil { - return nil, err - } - - return bz, nil + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + return p.HandleMethod(ctx, contract, stateDB, method, args) + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/evidence/evidence.go b/precompiles/evidence/evidence.go index d9db833b0..d4d916d13 100644 --- a/precompiles/evidence/evidence.go +++ b/precompiles/evidence/evidence.go @@ -3,10 +3,10 @@ package evidence import ( "embed" "fmt" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -81,48 +81,24 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract evidence methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) - if err != nil { - return nil, err - } - - // Start the balance change handler before executing the precompile. - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - // This handles any out of gas errors that may occur during the execution of a precompile tx or query. - // It avoids panics and returns the out of gas error so the EVM can continue gracefully. - defer cmn.HandleGasError(ctx, contract, initialGas, &err)() - - switch method.Name { - // evidence transactions - case SubmitEvidenceMethod: - bz, err = p.SubmitEvidence(ctx, contract, stateDB, method, args) - // evidence queries - case EvidenceMethod: - bz, err = p.Evidence(ctx, method, args) - case GetAllEvidenceMethod: - bz, err = p.GetAllEvidence(ctx, method, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - // Process the native balance changes after the method execution. - if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { - return nil, err - } - - return bz, nil +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // evidence transactions + case SubmitEvidenceMethod: + return p.SubmitEvidence(ctx, contract, stateDB, method, args) + // evidence queries + case EvidenceMethod: + return p.Evidence(ctx, method, args) + case GetAllEvidenceMethod: + return p.GetAllEvidence(ctx, method, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index 447fab588..43171b533 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -3,10 +3,10 @@ package gov import ( "embed" "fmt" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -85,72 +85,47 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract gov methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) - if err != nil { - return nil, err - } - - // Start the balance change handler before executing the precompile. - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - // This handles any out of gas errors that may occur during the execution of a precompile tx or query. - // It avoids panics and returns the out of gas error so the EVM can continue gracefully. - defer cmn.HandleGasError(ctx, contract, initialGas, &err)() - - switch method.Name { - // gov transactions - case VoteMethod: - bz, err = p.Vote(ctx, contract, stateDB, method, args) - case VoteWeightedMethod: - bz, err = p.VoteWeighted(ctx, contract, stateDB, method, args) - case SubmitProposalMethod: - bz, err = p.SubmitProposal(ctx, contract, stateDB, method, args) - case DepositMethod: - bz, err = p.Deposit(ctx, contract, stateDB, method, args) - case CancelProposalMethod: - bz, err = p.CancelProposal(ctx, contract, stateDB, method, args) - - // gov queries - case GetVoteMethod: - bz, err = p.GetVote(ctx, method, contract, args) - case GetVotesMethod: - bz, err = p.GetVotes(ctx, method, contract, args) - case GetDepositMethod: - bz, err = p.GetDeposit(ctx, method, contract, args) - case GetDepositsMethod: - bz, err = p.GetDeposits(ctx, method, contract, args) - case GetTallyResultMethod: - bz, err = p.GetTallyResult(ctx, method, contract, args) - case GetProposalMethod: - bz, err = p.GetProposal(ctx, method, contract, args) - case GetProposalsMethod: - bz, err = p.GetProposals(ctx, method, contract, args) - case GetParamsMethod: - bz, err = p.GetParams(ctx, method, contract, args) - case GetConstitutionMethod: - bz, err = p.GetConstitution(ctx, method, contract, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - // Process the native balance changes after the method execution. - err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB) - if err != nil { - return nil, err - } - - return bz, nil +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // gov transactions + case VoteMethod: + return p.Vote(ctx, contract, stateDB, method, args) + case VoteWeightedMethod: + return p.VoteWeighted(ctx, contract, stateDB, method, args) + case SubmitProposalMethod: + return p.SubmitProposal(ctx, contract, stateDB, method, args) + case DepositMethod: + return p.Deposit(ctx, contract, stateDB, method, args) + case CancelProposalMethod: + return p.CancelProposal(ctx, contract, stateDB, method, args) + + // gov queries + case GetVoteMethod: + return p.GetVote(ctx, method, contract, args) + case GetVotesMethod: + return p.GetVotes(ctx, method, contract, args) + case GetDepositMethod: + return p.GetDeposit(ctx, method, contract, args) + case GetDepositsMethod: + return p.GetDeposits(ctx, method, contract, args) + case GetTallyResultMethod: + return p.GetTallyResult(ctx, method, contract, args) + case GetProposalMethod: + return p.GetProposal(ctx, method, contract, args) + case GetProposalsMethod: + return p.GetProposals(ctx, method, contract, args) + case GetParamsMethod: + return p.GetParams(ctx, method, contract, args) + case GetConstitutionMethod: + return p.GetConstitution(ctx, method, contract, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/ics20/ics20.go b/precompiles/ics20/ics20.go index 06cb7dac7..2bf7d70a0 100644 --- a/precompiles/ics20/ics20.go +++ b/precompiles/ics20/ics20.go @@ -3,10 +3,11 @@ package ics20 import ( "embed" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -91,50 +92,26 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract IBC transfer methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) - if err != nil { - return nil, err - } - - // Start the balance change handler before executing the precompile. - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - // This handles any out of gas errors that may occur during the execution of a precompile tx or query. - // It avoids panics and returns the out of gas error so the EVM can continue gracefully. - defer cmn.HandleGasError(ctx, contract, initialGas, &err)() - - switch method.Name { - // ICS20 transactions - case TransferMethod: - bz, err = p.Transfer(ctx, contract, stateDB, method, args) - // ICS20 queries - case DenomMethod: - bz, err = p.Denom(ctx, contract, method, args) - case DenomsMethod: - bz, err = p.Denoms(ctx, contract, method, args) - case DenomHashMethod: - bz, err = p.DenomHash(ctx, contract, method, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - // Process the native balance changes after the method execution. - if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { - return nil, err - } - - return bz, nil +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // ICS20 transactions + case TransferMethod: + return p.Transfer(ctx, contract, stateDB, method, args) + // ICS20 queries + case DenomMethod: + return p.Denom(ctx, contract, method, args) + case DenomsMethod: + return p.Denoms(ctx, contract, method, args) + case DenomHashMethod: + return p.DenomHash(ctx, contract, method, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/slashing/slashing.go b/precompiles/slashing/slashing.go index 352bde020..73c953600 100644 --- a/precompiles/slashing/slashing.go +++ b/precompiles/slashing/slashing.go @@ -3,10 +3,10 @@ package slashing import ( "embed" "fmt" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -81,48 +81,24 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract slashing methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) - if err != nil { - return nil, err - } - - // Start the balance change handler before executing the precompile. - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - // This handles any out of gas errors that may occur during the execution of a precompile tx or query. - // It avoids panics and returns the out of gas error so the EVM can continue gracefully. - defer cmn.HandleGasError(ctx, contract, initialGas, &err)() - - switch method.Name { - // slashing transactions - case UnjailMethod: - bz, err = p.Unjail(ctx, method, stateDB, contract, args) - // slashing queries - case GetSigningInfoMethod: - bz, err = p.GetSigningInfo(ctx, method, contract, args) - case GetSigningInfosMethod: - bz, err = p.GetSigningInfos(ctx, method, contract, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - // Process the native balance changes after the method execution. - if err := p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { - return nil, err - } - - return bz, nil +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // slashing transactions + case UnjailMethod: + return p.Unjail(ctx, method, stateDB, contract, args) + // slashing queries + case GetSigningInfoMethod: + return p.GetSigningInfo(ctx, method, contract, args) + case GetSigningInfosMethod: + return p.GetSigningInfos(ctx, method, contract, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 56fa9e53f..2a34e693a 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -2,10 +2,10 @@ package staking import ( "embed" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -80,64 +80,41 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract staking methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) - if err != nil { - return nil, err - } - - // Start the balance change handler before executing the precompile. - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - // This handles any out of gas errors that may occur during the execution of a precompile tx or query. - // It avoids panics and returns the out of gas error so the EVM can continue gracefully. - defer cmn.HandleGasError(ctx, contract, initialGas, &err)() - - switch method.Name { - // Staking transactions - case CreateValidatorMethod: - bz, err = p.CreateValidator(ctx, contract, stateDB, method, args) - case EditValidatorMethod: - bz, err = p.EditValidator(ctx, contract, stateDB, method, args) - case DelegateMethod: - bz, err = p.Delegate(ctx, contract, stateDB, method, args) - case UndelegateMethod: - bz, err = p.Undelegate(ctx, contract, stateDB, method, args) - case RedelegateMethod: - bz, err = p.Redelegate(ctx, contract, stateDB, method, args) - case CancelUnbondingDelegationMethod: - bz, err = p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) - // Staking queries - case DelegationMethod: - bz, err = p.Delegation(ctx, contract, method, args) - case UnbondingDelegationMethod: - bz, err = p.UnbondingDelegation(ctx, contract, method, args) - case ValidatorMethod: - bz, err = p.Validator(ctx, method, contract, args) - case ValidatorsMethod: - bz, err = p.Validators(ctx, method, contract, args) - case RedelegationMethod: - bz, err = p.Redelegation(ctx, method, contract, args) - case RedelegationsMethod: - bz, err = p.Redelegations(ctx, method, contract, args) - } - - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - // Process the native balance changes after the method execution. - if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { - return nil, err - } - - return bz, nil +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // Staking transactions + case CreateValidatorMethod: + return p.CreateValidator(ctx, contract, stateDB, method, args) + case EditValidatorMethod: + return p.EditValidator(ctx, contract, stateDB, method, args) + case DelegateMethod: + return p.Delegate(ctx, contract, stateDB, method, args) + case UndelegateMethod: + return p.Undelegate(ctx, contract, stateDB, method, args) + case RedelegateMethod: + return p.Redelegate(ctx, contract, stateDB, method, args) + case CancelUnbondingDelegationMethod: + return p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) + // Staking queries + case DelegationMethod: + return p.Delegation(ctx, contract, method, args) + case UnbondingDelegationMethod: + return p.UnbondingDelegation(ctx, contract, method, args) + case ValidatorMethod: + return p.Validator(ctx, method, contract, args) + case ValidatorsMethod: + return p.Validators(ctx, method, contract, args) + case RedelegationMethod: + return p.Redelegation(ctx, method, contract, args) + case RedelegationsMethod: + return p.Redelegations(ctx, method, contract, args) + } + return nil, nil + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/werc20/werc20.go b/precompiles/werc20/werc20.go index 607eefe59..b0fe36e7c 100644 --- a/precompiles/werc20/werc20.go +++ b/precompiles/werc20/werc20.go @@ -3,11 +3,12 @@ package werc20 import ( "embed" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/evm/x/vm/statedb" "slices" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -106,48 +107,23 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract WERC20 methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) - if err != nil { - return nil, err - } - - // Start the balance change handler before executing the precompile. - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - // This handles any out of gas errors that may occur during the execution of - // a precompile tx or query. It avoids panics and returns the out of gas error so - // the EVM can continue gracefully. - defer cmn.HandleGasError(ctx, contract, initialGas, &err)() - - switch { - case method.Type == abi.Fallback, - method.Type == abi.Receive, - method.Name == DepositMethod: - bz, err = p.Deposit(ctx, contract, stateDB) - case method.Name == WithdrawMethod: - bz, err = p.Withdraw(ctx, contract, stateDB, args) - default: - // ERC20 transactions and queries - bz, err = p.HandleMethod(ctx, contract, stateDB, method, args) - } - - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - // Process the native balance changes after the method execution. - if err := p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { - return nil, err - } - - return bz, nil +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch { + case method.Type == abi.Fallback, + method.Type == abi.Receive, + method.Name == DepositMethod: + return p.Deposit(ctx, contract, stateDB) + case method.Name == WithdrawMethod: + return p.Withdraw(ctx, contract, stateDB, args) + default: + // ERC20 transactions and queries + return p.HandleMethod(ctx, contract, stateDB, method, args) + } + }, + ) } // IsTransaction returns true if the given method name correspond to a From b532fd547d56ce6b3d82562f0be5133da3ca6bb3 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Thu, 3 Jul 2025 18:58:55 +0900 Subject: [PATCH 16/19] test(precompile/common): improve unit test for balance handler --- precompiles/common/balance_handler_test.go | 137 +++++++++++++++------ 1 file changed, 99 insertions(+), 38 deletions(-) diff --git a/precompiles/common/balance_handler_test.go b/precompiles/common/balance_handler_test.go index efc74e52f..745d3efb9 100644 --- a/precompiles/common/balance_handler_test.go +++ b/precompiles/common/balance_handler_test.go @@ -31,47 +31,108 @@ func setupBalanceHandlerTest(t *testing.T) { } func TestParseHexAddress(t *testing.T) { - setupBalanceHandlerTest(t) - - _, addrs, err := testutil.GeneratePrivKeyAddressPairs(1) - require.NoError(t, err) - accAddr := addrs[0] - - // valid address - ev := sdk.NewEvent("bank", sdk.NewAttribute(banktypes.AttributeKeySpender, accAddr.String())) - addr, err := parseHexAddress(ev, banktypes.AttributeKeySpender) - require.NoError(t, err) - require.Equal(t, common.Address(accAddr.Bytes()), addr) - - // missing attribute - ev = sdk.NewEvent("bank") - _, err = parseHexAddress(ev, banktypes.AttributeKeySpender) - require.Error(t, err) - - // invalid address - ev = sdk.NewEvent("bank", sdk.NewAttribute(banktypes.AttributeKeySpender, "invalid")) - _, err = parseHexAddress(ev, banktypes.AttributeKeySpender) - require.Error(t, err) + var accAddr sdk.AccAddress + + testCases := []struct { + name string + maleate func() sdk.Event + key string + expAddr common.Address + expError bool + }{ + { + name: "valid address", + maleate: func() sdk.Event { + return sdk.NewEvent("bank", sdk.NewAttribute(banktypes.AttributeKeySpender, accAddr.String())) + }, + key: banktypes.AttributeKeySpender, + expError: false, + }, + { + name: "missing attribute", + maleate: func() sdk.Event { + return sdk.NewEvent("bank") + }, + key: banktypes.AttributeKeySpender, + expError: true, + }, + { + name: "invalid address", + maleate: func() sdk.Event { + return sdk.NewEvent("bank", sdk.NewAttribute(banktypes.AttributeKeySpender, "invalid")) + }, + key: banktypes.AttributeKeySpender, + expError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + setupBalanceHandlerTest(t) + + _, addrs, err := testutil.GeneratePrivKeyAddressPairs(1) + require.NoError(t, err) + accAddr = addrs[0] + + event := tc.maleate() + + addr, err := parseHexAddress(event, tc.key) + if tc.expError { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, common.Address(accAddr.Bytes()), addr) + }) + } } func TestParseAmount(t *testing.T) { - setupBalanceHandlerTest(t) - - coinStr := sdk.NewCoins(sdk.NewInt64Coin(evmtypes.GetEVMCoinDenom(), 5)).String() - ev := sdk.NewEvent("bank", sdk.NewAttribute(sdk.AttributeKeyAmount, coinStr)) - amt, err := parseAmount(ev) - require.NoError(t, err) - require.True(t, amt.Eq(uint256.NewInt(5))) - - // missing amount - ev = sdk.NewEvent("bank") - _, err = parseAmount(ev) - require.Error(t, err) - - // invalid coins - ev = sdk.NewEvent("bank", sdk.NewAttribute(sdk.AttributeKeyAmount, "invalid")) - _, err = parseAmount(ev) - require.Error(t, err) + testCases := []struct { + name string + maleate func() sdk.Event + expAmt *uint256.Int + expError bool + }{ + { + name: "valid amount", + maleate: func() sdk.Event { + coinStr := sdk.NewCoins(sdk.NewInt64Coin(evmtypes.GetEVMCoinDenom(), 5)).String() + return sdk.NewEvent("bank", sdk.NewAttribute(sdk.AttributeKeyAmount, coinStr)) + }, + expAmt: uint256.NewInt(5), + }, + { + name: "missing amount", + maleate: func() sdk.Event { + return sdk.NewEvent("bank") + }, + expError: true, + }, + { + name: "invalid coins", + maleate: func() sdk.Event { + return sdk.NewEvent("bank", sdk.NewAttribute(sdk.AttributeKeyAmount, "invalid")) + }, + expError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + setupBalanceHandlerTest(t) + + amt, err := parseAmount(tc.maleate()) + if tc.expError { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.True(t, amt.Eq(tc.expAmt)) + }) + } } func TestAfterBalanceChange(t *testing.T) { From 25b89f32b7553e1b38d80d8c789f3638f966e5d4 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Thu, 3 Jul 2025 20:47:42 +0900 Subject: [PATCH 17/19] refactor(precompiles): separate common logic --- precompiles/common/precompile.go | 81 +++++++++++----------- precompiles/common/types.go | 11 +++ precompiles/distribution/distribution.go | 85 ++++++++++++------------ precompiles/erc20/erc20.go | 8 +-- precompiles/evidence/evidence.go | 35 +++++----- precompiles/gov/gov.go | 81 +++++++++++----------- precompiles/ics20/ics20.go | 40 +++++------ precompiles/slashing/slashing.go | 35 +++++----- precompiles/staking/staking.go | 71 ++++++++++---------- precompiles/werc20/werc20.go | 36 +++++----- 10 files changed, 246 insertions(+), 237 deletions(-) diff --git a/precompiles/common/precompile.go b/precompiles/common/precompile.go index e188dae8e..2a9f679f5 100644 --- a/precompiles/common/precompile.go +++ b/precompiles/common/precompile.go @@ -58,6 +58,49 @@ func (p Precompile) RequiredGas(input []byte, isTransaction bool) uint64 { return p.KvGasConfig.ReadCostFlat + (p.KvGasConfig.ReadCostPerByte * uint64(len(argsBz))) } +// SetupAndRun wraps the execution of a precompile method with +// the necessary setup, balance handling and gas accounting logic. +// Precompile implementations can use this helper to avoid repetitive boilerplate +// in their `Run` methods. +func (p Precompile) SetupAndRun( + evm *vm.EVM, + contract *vm.Contract, + readOnly bool, + isTransaction func(*abi.Method) bool, + handleMethod HandleMethodFunc, +) (bz []byte, err error) { + // Setup precompile method execution + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, isTransaction) + if err != nil { + return nil, err + } + + // Handles the out of gas panic by resetting the gas meter and returning an error + defer HandleGasError(ctx, contract, initialGas, &err)() + + // Record the length of events emitted before precompile method execution + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + // Executes precompile method + bz, err = handleMethod(ctx, contract, stateDB, method, args) + if err != nil { + return nil, err + } + + // Handles native balance changes parsed from sdk.Events + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + // Consume Gas + cost := ctx.GasMeter().GasConsumed() - initialGas + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + return bz, nil +} + // RunSetup runs the initial setup required to run a transaction or a query. // It returns the sdk Context, EVM stateDB, ABI method, initial gas and calling arguments. func (p Precompile) RunSetup( @@ -231,41 +274,3 @@ func (p *Precompile) GetBalanceHandler() *BalanceHandler { } return p.balanceHandler } - -// ExecuteWithBalanceHandling wraps the execution of a precompile method with -// the necessary balance handling and gas accounting logic. Precompile -// implementations can use this helper to avoid repetitive boilerplate in their -// `Run` methods. -func (p Precompile) ExecuteWithBalanceHandling( - evm *vm.EVM, - contract *vm.Contract, - readOnly bool, - isTransaction func(*abi.Method) bool, - handle func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error), -) (bz []byte, err error) { - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, isTransaction) - if err != nil { - return nil, err - } - - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - defer HandleGasError(ctx, contract, initialGas, &err)() - - bz, err = handle(ctx, contract, stateDB, method, args) - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { - return nil, err - } - - return bz, nil -} diff --git a/precompiles/common/types.go b/precompiles/common/types.go index 4e1d568b9..23f235e4c 100644 --- a/precompiles/common/types.go +++ b/precompiles/common/types.go @@ -8,11 +8,22 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/vm" ) // TrueValue is the byte array representing a true value in solidity. var TrueValue = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1} +// HandleMethodFunc defines precompile method handler fuction type. +type HandleMethodFunc func( + ctx sdk.Context, + contract *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) + // ICS20Allocation defines the spend limit for a particular port and channel. // We need this to be able to unpack to big.Int instead of math.Int. type ICS20Allocation struct { diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index ee81249ec..c1075313c 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -3,8 +3,8 @@ package distribution import ( "embed" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -86,47 +86,48 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract distribution methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // Custom transactions - case ClaimRewardsMethod: - return p.ClaimRewards(ctx, contract, stateDB, method, args) - // Distribution transactions - case SetWithdrawAddressMethod: - return p.SetWithdrawAddress(ctx, contract, stateDB, method, args) - case WithdrawDelegatorRewardMethod: - return p.WithdrawDelegatorReward(ctx, contract, stateDB, method, args) - case WithdrawValidatorCommissionMethod: - return p.WithdrawValidatorCommission(ctx, contract, stateDB, method, args) - case FundCommunityPoolMethod: - return p.FundCommunityPool(ctx, contract, stateDB, method, args) - case DepositValidatorRewardsPoolMethod: - return p.DepositValidatorRewardsPool(ctx, contract, stateDB, method, args) - // Distribution queries - case ValidatorDistributionInfoMethod: - return p.ValidatorDistributionInfo(ctx, contract, method, args) - case ValidatorOutstandingRewardsMethod: - return p.ValidatorOutstandingRewards(ctx, contract, method, args) - case ValidatorCommissionMethod: - return p.ValidatorCommission(ctx, contract, method, args) - case ValidatorSlashesMethod: - return p.ValidatorSlashes(ctx, contract, method, args) - case DelegationRewardsMethod: - return p.DelegationRewards(ctx, contract, method, args) - case DelegationTotalRewardsMethod: - return p.DelegationTotalRewards(ctx, contract, method, args) - case DelegatorValidatorsMethod: - return p.DelegatorValidators(ctx, contract, method, args) - case DelegatorWithdrawAddressMethod: - return p.DelegatorWithdrawAddress(ctx, contract, method, args) - case CommunityPoolMethod: - return p.CommunityPool(ctx, contract, method, args) - } - return nil, nil - }, - ) + return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) +} + +// HandleMethod handles the execution of each method +func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // Custom transactions + case ClaimRewardsMethod: + return p.ClaimRewards(ctx, contract, stateDB, method, args) + // Distribution transactions + case SetWithdrawAddressMethod: + return p.SetWithdrawAddress(ctx, contract, stateDB, method, args) + case WithdrawDelegatorRewardMethod: + return p.WithdrawDelegatorReward(ctx, contract, stateDB, method, args) + case WithdrawValidatorCommissionMethod: + return p.WithdrawValidatorCommission(ctx, contract, stateDB, method, args) + case FundCommunityPoolMethod: + return p.FundCommunityPool(ctx, contract, stateDB, method, args) + case DepositValidatorRewardsPoolMethod: + return p.DepositValidatorRewardsPool(ctx, contract, stateDB, method, args) + // Distribution queries + case ValidatorDistributionInfoMethod: + return p.ValidatorDistributionInfo(ctx, contract, method, args) + case ValidatorOutstandingRewardsMethod: + return p.ValidatorOutstandingRewards(ctx, contract, method, args) + case ValidatorCommissionMethod: + return p.ValidatorCommission(ctx, contract, method, args) + case ValidatorSlashesMethod: + return p.ValidatorSlashes(ctx, contract, method, args) + case DelegationRewardsMethod: + return p.DelegationRewards(ctx, contract, method, args) + case DelegationTotalRewardsMethod: + return p.DelegationTotalRewards(ctx, contract, method, args) + case DelegatorValidatorsMethod: + return p.DelegatorValidators(ctx, contract, method, args) + case DelegatorWithdrawAddressMethod: + return p.DelegatorWithdrawAddress(ctx, contract, method, args) + case CommunityPoolMethod: + return p.CommunityPool(ctx, contract, method, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/erc20/erc20.go b/precompiles/erc20/erc20.go index b8ccf1986..4c110a08f 100644 --- a/precompiles/erc20/erc20.go +++ b/precompiles/erc20/erc20.go @@ -3,7 +3,6 @@ package erc20 import ( "embed" "fmt" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/core/vm" @@ -144,12 +143,7 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]by return nil, fmt.Errorf(ErrCannotReceiveFunds, contract.Value().String()) } - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - return p.HandleMethod(ctx, contract, stateDB, method, args) - }, - ) + return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/evidence/evidence.go b/precompiles/evidence/evidence.go index d4d916d13..561484601 100644 --- a/precompiles/evidence/evidence.go +++ b/precompiles/evidence/evidence.go @@ -3,7 +3,6 @@ package evidence import ( "embed" "fmt" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -82,23 +81,23 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract evidence methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // evidence transactions - case SubmitEvidenceMethod: - return p.SubmitEvidence(ctx, contract, stateDB, method, args) - // evidence queries - case EvidenceMethod: - return p.Evidence(ctx, method, args) - case GetAllEvidenceMethod: - return p.GetAllEvidence(ctx, method, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - }, - ) + return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) +} + +// HandleMethod handles the execution of each method +func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // evidence transactions + case SubmitEvidenceMethod: + return p.SubmitEvidence(ctx, contract, stateDB, method, args) + // evidence queries + case EvidenceMethod: + return p.Evidence(ctx, method, args) + case GetAllEvidenceMethod: + return p.GetAllEvidence(ctx, method, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index 43171b533..bd7a3c3f9 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -3,7 +3,6 @@ package gov import ( "embed" "fmt" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -86,46 +85,46 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract gov methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // gov transactions - case VoteMethod: - return p.Vote(ctx, contract, stateDB, method, args) - case VoteWeightedMethod: - return p.VoteWeighted(ctx, contract, stateDB, method, args) - case SubmitProposalMethod: - return p.SubmitProposal(ctx, contract, stateDB, method, args) - case DepositMethod: - return p.Deposit(ctx, contract, stateDB, method, args) - case CancelProposalMethod: - return p.CancelProposal(ctx, contract, stateDB, method, args) - - // gov queries - case GetVoteMethod: - return p.GetVote(ctx, method, contract, args) - case GetVotesMethod: - return p.GetVotes(ctx, method, contract, args) - case GetDepositMethod: - return p.GetDeposit(ctx, method, contract, args) - case GetDepositsMethod: - return p.GetDeposits(ctx, method, contract, args) - case GetTallyResultMethod: - return p.GetTallyResult(ctx, method, contract, args) - case GetProposalMethod: - return p.GetProposal(ctx, method, contract, args) - case GetProposalsMethod: - return p.GetProposals(ctx, method, contract, args) - case GetParamsMethod: - return p.GetParams(ctx, method, contract, args) - case GetConstitutionMethod: - return p.GetConstitution(ctx, method, contract, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - }, - ) + return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) +} + +// HandleMethod handles the execution of each method +func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // gov transactions + case VoteMethod: + return p.Vote(ctx, contract, stateDB, method, args) + case VoteWeightedMethod: + return p.VoteWeighted(ctx, contract, stateDB, method, args) + case SubmitProposalMethod: + return p.SubmitProposal(ctx, contract, stateDB, method, args) + case DepositMethod: + return p.Deposit(ctx, contract, stateDB, method, args) + case CancelProposalMethod: + return p.CancelProposal(ctx, contract, stateDB, method, args) + + // gov queries + case GetVoteMethod: + return p.GetVote(ctx, method, contract, args) + case GetVotesMethod: + return p.GetVotes(ctx, method, contract, args) + case GetDepositMethod: + return p.GetDeposit(ctx, method, contract, args) + case GetDepositsMethod: + return p.GetDeposits(ctx, method, contract, args) + case GetTallyResultMethod: + return p.GetTallyResult(ctx, method, contract, args) + case GetProposalMethod: + return p.GetProposal(ctx, method, contract, args) + case GetProposalsMethod: + return p.GetProposals(ctx, method, contract, args) + case GetParamsMethod: + return p.GetParams(ctx, method, contract, args) + case GetConstitutionMethod: + return p.GetConstitution(ctx, method, contract, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/ics20/ics20.go b/precompiles/ics20/ics20.go index 2bf7d70a0..e4d3a4ce2 100644 --- a/precompiles/ics20/ics20.go +++ b/precompiles/ics20/ics20.go @@ -3,8 +3,8 @@ package ics20 import ( "embed" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -93,25 +93,25 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract IBC transfer methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // ICS20 transactions - case TransferMethod: - return p.Transfer(ctx, contract, stateDB, method, args) - // ICS20 queries - case DenomMethod: - return p.Denom(ctx, contract, method, args) - case DenomsMethod: - return p.Denoms(ctx, contract, method, args) - case DenomHashMethod: - return p.DenomHash(ctx, contract, method, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - }, - ) + return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) +} + +// HandleMethod handles the execution of each method +func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // ICS20 transactions + case TransferMethod: + return p.Transfer(ctx, contract, stateDB, method, args) + // ICS20 queries + case DenomMethod: + return p.Denom(ctx, contract, method, args) + case DenomsMethod: + return p.Denoms(ctx, contract, method, args) + case DenomHashMethod: + return p.DenomHash(ctx, contract, method, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/slashing/slashing.go b/precompiles/slashing/slashing.go index 73c953600..fe8db340e 100644 --- a/precompiles/slashing/slashing.go +++ b/precompiles/slashing/slashing.go @@ -3,7 +3,6 @@ package slashing import ( "embed" "fmt" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -82,23 +81,23 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract slashing methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // slashing transactions - case UnjailMethod: - return p.Unjail(ctx, method, stateDB, contract, args) - // slashing queries - case GetSigningInfoMethod: - return p.GetSigningInfo(ctx, method, contract, args) - case GetSigningInfosMethod: - return p.GetSigningInfos(ctx, method, contract, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - }, - ) + return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) +} + +// HandleMethod handles the execution of each method +func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // slashing transactions + case UnjailMethod: + return p.Unjail(ctx, method, stateDB, contract, args) + // slashing queries + case GetSigningInfoMethod: + return p.GetSigningInfo(ctx, method, contract, args) + case GetSigningInfosMethod: + return p.GetSigningInfos(ctx, method, contract, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 2a34e693a..2ef54ba47 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -2,7 +2,7 @@ package staking import ( "embed" - "github.com/cosmos/evm/x/vm/statedb" + "fmt" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -81,40 +81,41 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract staking methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // Staking transactions - case CreateValidatorMethod: - return p.CreateValidator(ctx, contract, stateDB, method, args) - case EditValidatorMethod: - return p.EditValidator(ctx, contract, stateDB, method, args) - case DelegateMethod: - return p.Delegate(ctx, contract, stateDB, method, args) - case UndelegateMethod: - return p.Undelegate(ctx, contract, stateDB, method, args) - case RedelegateMethod: - return p.Redelegate(ctx, contract, stateDB, method, args) - case CancelUnbondingDelegationMethod: - return p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) - // Staking queries - case DelegationMethod: - return p.Delegation(ctx, contract, method, args) - case UnbondingDelegationMethod: - return p.UnbondingDelegation(ctx, contract, method, args) - case ValidatorMethod: - return p.Validator(ctx, method, contract, args) - case ValidatorsMethod: - return p.Validators(ctx, method, contract, args) - case RedelegationMethod: - return p.Redelegation(ctx, method, contract, args) - case RedelegationsMethod: - return p.Redelegations(ctx, method, contract, args) - } - return nil, nil - }, - ) + return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) +} + +// HandleMethod handles the execution of each method +func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // Staking transactions + case CreateValidatorMethod: + return p.CreateValidator(ctx, contract, stateDB, method, args) + case EditValidatorMethod: + return p.EditValidator(ctx, contract, stateDB, method, args) + case DelegateMethod: + return p.Delegate(ctx, contract, stateDB, method, args) + case UndelegateMethod: + return p.Undelegate(ctx, contract, stateDB, method, args) + case RedelegateMethod: + return p.Redelegate(ctx, contract, stateDB, method, args) + case CancelUnbondingDelegationMethod: + return p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) + // Staking queries + case DelegationMethod: + return p.Delegation(ctx, contract, method, args) + case UnbondingDelegationMethod: + return p.UnbondingDelegation(ctx, contract, method, args) + case ValidatorMethod: + return p.Validator(ctx, method, contract, args) + case ValidatorsMethod: + return p.Validators(ctx, method, contract, args) + case RedelegationMethod: + return p.Redelegation(ctx, method, contract, args) + case RedelegationsMethod: + return p.Redelegations(ctx, method, contract, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/werc20/werc20.go b/precompiles/werc20/werc20.go index b0fe36e7c..74baebb94 100644 --- a/precompiles/werc20/werc20.go +++ b/precompiles/werc20/werc20.go @@ -3,10 +3,10 @@ package werc20 import ( "embed" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/evm/x/vm/statedb" "slices" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" @@ -108,22 +108,22 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract WERC20 methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch { - case method.Type == abi.Fallback, - method.Type == abi.Receive, - method.Name == DepositMethod: - return p.Deposit(ctx, contract, stateDB) - case method.Name == WithdrawMethod: - return p.Withdraw(ctx, contract, stateDB, args) - default: - // ERC20 transactions and queries - return p.HandleMethod(ctx, contract, stateDB, method, args) - } - }, - ) + return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) +} + +// HandleMethod handles the execution of each method +func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch { + case method.Type == abi.Fallback, + method.Type == abi.Receive, + method.Name == DepositMethod: + return p.Deposit(ctx, contract, stateDB) + case method.Name == WithdrawMethod: + return p.Withdraw(ctx, contract, stateDB, args) + default: + // ERC20 transactions and queries + return p.HandleMethod(ctx, contract, stateDB, method, args) + } } // IsTransaction returns true if the given method name correspond to a From 36ac73122bb3ea1eae9221b458f0ecaf35523960 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Thu, 3 Jul 2025 21:50:47 +0900 Subject: [PATCH 18/19] Revert "refactor(precompiles): separate common logic" This reverts commit 25b89f32b7553e1b38d80d8c789f3638f966e5d4. --- precompiles/common/precompile.go | 81 +++++++++++----------- precompiles/common/types.go | 11 --- precompiles/distribution/distribution.go | 85 ++++++++++++------------ precompiles/erc20/erc20.go | 8 ++- precompiles/evidence/evidence.go | 35 +++++----- precompiles/gov/gov.go | 81 +++++++++++----------- precompiles/ics20/ics20.go | 40 +++++------ precompiles/slashing/slashing.go | 35 +++++----- precompiles/staking/staking.go | 71 ++++++++++---------- precompiles/werc20/werc20.go | 36 +++++----- 10 files changed, 237 insertions(+), 246 deletions(-) diff --git a/precompiles/common/precompile.go b/precompiles/common/precompile.go index 2a9f679f5..e188dae8e 100644 --- a/precompiles/common/precompile.go +++ b/precompiles/common/precompile.go @@ -58,49 +58,6 @@ func (p Precompile) RequiredGas(input []byte, isTransaction bool) uint64 { return p.KvGasConfig.ReadCostFlat + (p.KvGasConfig.ReadCostPerByte * uint64(len(argsBz))) } -// SetupAndRun wraps the execution of a precompile method with -// the necessary setup, balance handling and gas accounting logic. -// Precompile implementations can use this helper to avoid repetitive boilerplate -// in their `Run` methods. -func (p Precompile) SetupAndRun( - evm *vm.EVM, - contract *vm.Contract, - readOnly bool, - isTransaction func(*abi.Method) bool, - handleMethod HandleMethodFunc, -) (bz []byte, err error) { - // Setup precompile method execution - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, isTransaction) - if err != nil { - return nil, err - } - - // Handles the out of gas panic by resetting the gas meter and returning an error - defer HandleGasError(ctx, contract, initialGas, &err)() - - // Record the length of events emitted before precompile method execution - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - // Executes precompile method - bz, err = handleMethod(ctx, contract, stateDB, method, args) - if err != nil { - return nil, err - } - - // Handles native balance changes parsed from sdk.Events - if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { - return nil, err - } - - // Consume Gas - cost := ctx.GasMeter().GasConsumed() - initialGas - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - return bz, nil -} - // RunSetup runs the initial setup required to run a transaction or a query. // It returns the sdk Context, EVM stateDB, ABI method, initial gas and calling arguments. func (p Precompile) RunSetup( @@ -274,3 +231,41 @@ func (p *Precompile) GetBalanceHandler() *BalanceHandler { } return p.balanceHandler } + +// ExecuteWithBalanceHandling wraps the execution of a precompile method with +// the necessary balance handling and gas accounting logic. Precompile +// implementations can use this helper to avoid repetitive boilerplate in their +// `Run` methods. +func (p Precompile) ExecuteWithBalanceHandling( + evm *vm.EVM, + contract *vm.Contract, + readOnly bool, + isTransaction func(*abi.Method) bool, + handle func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error), +) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, isTransaction) + if err != nil { + return nil, err + } + + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + defer HandleGasError(ctx, contract, initialGas, &err)() + + bz, err = handle(ctx, contract, stateDB, method, args) + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + return bz, nil +} diff --git a/precompiles/common/types.go b/precompiles/common/types.go index 23f235e4c..4e1d568b9 100644 --- a/precompiles/common/types.go +++ b/precompiles/common/types.go @@ -8,22 +8,11 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/core/vm" ) // TrueValue is the byte array representing a true value in solidity. var TrueValue = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1} -// HandleMethodFunc defines precompile method handler fuction type. -type HandleMethodFunc func( - ctx sdk.Context, - contract *vm.Contract, - stateDB vm.StateDB, - method *abi.Method, - args []interface{}, -) ([]byte, error) - // ICS20Allocation defines the spend limit for a particular port and channel. // We need this to be able to unpack to big.Int instead of math.Int. type ICS20Allocation struct { diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index c1075313c..ee81249ec 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -3,8 +3,8 @@ package distribution import ( "embed" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -86,48 +86,47 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract distribution methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) -} - -// HandleMethod handles the execution of each method -func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // Custom transactions - case ClaimRewardsMethod: - return p.ClaimRewards(ctx, contract, stateDB, method, args) - // Distribution transactions - case SetWithdrawAddressMethod: - return p.SetWithdrawAddress(ctx, contract, stateDB, method, args) - case WithdrawDelegatorRewardMethod: - return p.WithdrawDelegatorReward(ctx, contract, stateDB, method, args) - case WithdrawValidatorCommissionMethod: - return p.WithdrawValidatorCommission(ctx, contract, stateDB, method, args) - case FundCommunityPoolMethod: - return p.FundCommunityPool(ctx, contract, stateDB, method, args) - case DepositValidatorRewardsPoolMethod: - return p.DepositValidatorRewardsPool(ctx, contract, stateDB, method, args) - // Distribution queries - case ValidatorDistributionInfoMethod: - return p.ValidatorDistributionInfo(ctx, contract, method, args) - case ValidatorOutstandingRewardsMethod: - return p.ValidatorOutstandingRewards(ctx, contract, method, args) - case ValidatorCommissionMethod: - return p.ValidatorCommission(ctx, contract, method, args) - case ValidatorSlashesMethod: - return p.ValidatorSlashes(ctx, contract, method, args) - case DelegationRewardsMethod: - return p.DelegationRewards(ctx, contract, method, args) - case DelegationTotalRewardsMethod: - return p.DelegationTotalRewards(ctx, contract, method, args) - case DelegatorValidatorsMethod: - return p.DelegatorValidators(ctx, contract, method, args) - case DelegatorWithdrawAddressMethod: - return p.DelegatorWithdrawAddress(ctx, contract, method, args) - case CommunityPoolMethod: - return p.CommunityPool(ctx, contract, method, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // Custom transactions + case ClaimRewardsMethod: + return p.ClaimRewards(ctx, contract, stateDB, method, args) + // Distribution transactions + case SetWithdrawAddressMethod: + return p.SetWithdrawAddress(ctx, contract, stateDB, method, args) + case WithdrawDelegatorRewardMethod: + return p.WithdrawDelegatorReward(ctx, contract, stateDB, method, args) + case WithdrawValidatorCommissionMethod: + return p.WithdrawValidatorCommission(ctx, contract, stateDB, method, args) + case FundCommunityPoolMethod: + return p.FundCommunityPool(ctx, contract, stateDB, method, args) + case DepositValidatorRewardsPoolMethod: + return p.DepositValidatorRewardsPool(ctx, contract, stateDB, method, args) + // Distribution queries + case ValidatorDistributionInfoMethod: + return p.ValidatorDistributionInfo(ctx, contract, method, args) + case ValidatorOutstandingRewardsMethod: + return p.ValidatorOutstandingRewards(ctx, contract, method, args) + case ValidatorCommissionMethod: + return p.ValidatorCommission(ctx, contract, method, args) + case ValidatorSlashesMethod: + return p.ValidatorSlashes(ctx, contract, method, args) + case DelegationRewardsMethod: + return p.DelegationRewards(ctx, contract, method, args) + case DelegationTotalRewardsMethod: + return p.DelegationTotalRewards(ctx, contract, method, args) + case DelegatorValidatorsMethod: + return p.DelegatorValidators(ctx, contract, method, args) + case DelegatorWithdrawAddressMethod: + return p.DelegatorWithdrawAddress(ctx, contract, method, args) + case CommunityPoolMethod: + return p.CommunityPool(ctx, contract, method, args) + } + return nil, nil + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/erc20/erc20.go b/precompiles/erc20/erc20.go index 4c110a08f..b8ccf1986 100644 --- a/precompiles/erc20/erc20.go +++ b/precompiles/erc20/erc20.go @@ -3,6 +3,7 @@ package erc20 import ( "embed" "fmt" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/core/vm" @@ -143,7 +144,12 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]by return nil, fmt.Errorf(ErrCannotReceiveFunds, contract.Value().String()) } - return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + return p.HandleMethod(ctx, contract, stateDB, method, args) + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/evidence/evidence.go b/precompiles/evidence/evidence.go index 561484601..d4d916d13 100644 --- a/precompiles/evidence/evidence.go +++ b/precompiles/evidence/evidence.go @@ -3,6 +3,7 @@ package evidence import ( "embed" "fmt" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -81,23 +82,23 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract evidence methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) -} - -// HandleMethod handles the execution of each method -func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // evidence transactions - case SubmitEvidenceMethod: - return p.SubmitEvidence(ctx, contract, stateDB, method, args) - // evidence queries - case EvidenceMethod: - return p.Evidence(ctx, method, args) - case GetAllEvidenceMethod: - return p.GetAllEvidence(ctx, method, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // evidence transactions + case SubmitEvidenceMethod: + return p.SubmitEvidence(ctx, contract, stateDB, method, args) + // evidence queries + case EvidenceMethod: + return p.Evidence(ctx, method, args) + case GetAllEvidenceMethod: + return p.GetAllEvidence(ctx, method, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index bd7a3c3f9..43171b533 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -3,6 +3,7 @@ package gov import ( "embed" "fmt" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -85,46 +86,46 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract gov methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) -} - -// HandleMethod handles the execution of each method -func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // gov transactions - case VoteMethod: - return p.Vote(ctx, contract, stateDB, method, args) - case VoteWeightedMethod: - return p.VoteWeighted(ctx, contract, stateDB, method, args) - case SubmitProposalMethod: - return p.SubmitProposal(ctx, contract, stateDB, method, args) - case DepositMethod: - return p.Deposit(ctx, contract, stateDB, method, args) - case CancelProposalMethod: - return p.CancelProposal(ctx, contract, stateDB, method, args) - - // gov queries - case GetVoteMethod: - return p.GetVote(ctx, method, contract, args) - case GetVotesMethod: - return p.GetVotes(ctx, method, contract, args) - case GetDepositMethod: - return p.GetDeposit(ctx, method, contract, args) - case GetDepositsMethod: - return p.GetDeposits(ctx, method, contract, args) - case GetTallyResultMethod: - return p.GetTallyResult(ctx, method, contract, args) - case GetProposalMethod: - return p.GetProposal(ctx, method, contract, args) - case GetProposalsMethod: - return p.GetProposals(ctx, method, contract, args) - case GetParamsMethod: - return p.GetParams(ctx, method, contract, args) - case GetConstitutionMethod: - return p.GetConstitution(ctx, method, contract, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // gov transactions + case VoteMethod: + return p.Vote(ctx, contract, stateDB, method, args) + case VoteWeightedMethod: + return p.VoteWeighted(ctx, contract, stateDB, method, args) + case SubmitProposalMethod: + return p.SubmitProposal(ctx, contract, stateDB, method, args) + case DepositMethod: + return p.Deposit(ctx, contract, stateDB, method, args) + case CancelProposalMethod: + return p.CancelProposal(ctx, contract, stateDB, method, args) + + // gov queries + case GetVoteMethod: + return p.GetVote(ctx, method, contract, args) + case GetVotesMethod: + return p.GetVotes(ctx, method, contract, args) + case GetDepositMethod: + return p.GetDeposit(ctx, method, contract, args) + case GetDepositsMethod: + return p.GetDeposits(ctx, method, contract, args) + case GetTallyResultMethod: + return p.GetTallyResult(ctx, method, contract, args) + case GetProposalMethod: + return p.GetProposal(ctx, method, contract, args) + case GetProposalsMethod: + return p.GetProposals(ctx, method, contract, args) + case GetParamsMethod: + return p.GetParams(ctx, method, contract, args) + case GetConstitutionMethod: + return p.GetConstitution(ctx, method, contract, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/ics20/ics20.go b/precompiles/ics20/ics20.go index e4d3a4ce2..2bf7d70a0 100644 --- a/precompiles/ics20/ics20.go +++ b/precompiles/ics20/ics20.go @@ -3,8 +3,8 @@ package ics20 import ( "embed" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -93,25 +93,25 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract IBC transfer methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) -} - -// HandleMethod handles the execution of each method -func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // ICS20 transactions - case TransferMethod: - return p.Transfer(ctx, contract, stateDB, method, args) - // ICS20 queries - case DenomMethod: - return p.Denom(ctx, contract, method, args) - case DenomsMethod: - return p.Denoms(ctx, contract, method, args) - case DenomHashMethod: - return p.DenomHash(ctx, contract, method, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // ICS20 transactions + case TransferMethod: + return p.Transfer(ctx, contract, stateDB, method, args) + // ICS20 queries + case DenomMethod: + return p.Denom(ctx, contract, method, args) + case DenomsMethod: + return p.Denoms(ctx, contract, method, args) + case DenomHashMethod: + return p.DenomHash(ctx, contract, method, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/slashing/slashing.go b/precompiles/slashing/slashing.go index fe8db340e..73c953600 100644 --- a/precompiles/slashing/slashing.go +++ b/precompiles/slashing/slashing.go @@ -3,6 +3,7 @@ package slashing import ( "embed" "fmt" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -81,23 +82,23 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract slashing methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) -} - -// HandleMethod handles the execution of each method -func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // slashing transactions - case UnjailMethod: - return p.Unjail(ctx, method, stateDB, contract, args) - // slashing queries - case GetSigningInfoMethod: - return p.GetSigningInfo(ctx, method, contract, args) - case GetSigningInfosMethod: - return p.GetSigningInfos(ctx, method, contract, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // slashing transactions + case UnjailMethod: + return p.Unjail(ctx, method, stateDB, contract, args) + // slashing queries + case GetSigningInfoMethod: + return p.GetSigningInfo(ctx, method, contract, args) + case GetSigningInfosMethod: + return p.GetSigningInfos(ctx, method, contract, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 2ef54ba47..2a34e693a 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -2,7 +2,7 @@ package staking import ( "embed" - "fmt" + "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -81,41 +81,40 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract staking methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) -} - -// HandleMethod handles the execution of each method -func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // Staking transactions - case CreateValidatorMethod: - return p.CreateValidator(ctx, contract, stateDB, method, args) - case EditValidatorMethod: - return p.EditValidator(ctx, contract, stateDB, method, args) - case DelegateMethod: - return p.Delegate(ctx, contract, stateDB, method, args) - case UndelegateMethod: - return p.Undelegate(ctx, contract, stateDB, method, args) - case RedelegateMethod: - return p.Redelegate(ctx, contract, stateDB, method, args) - case CancelUnbondingDelegationMethod: - return p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) - // Staking queries - case DelegationMethod: - return p.Delegation(ctx, contract, method, args) - case UnbondingDelegationMethod: - return p.UnbondingDelegation(ctx, contract, method, args) - case ValidatorMethod: - return p.Validator(ctx, method, contract, args) - case ValidatorsMethod: - return p.Validators(ctx, method, contract, args) - case RedelegationMethod: - return p.Redelegation(ctx, method, contract, args) - case RedelegationsMethod: - return p.Redelegations(ctx, method, contract, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch method.Name { + // Staking transactions + case CreateValidatorMethod: + return p.CreateValidator(ctx, contract, stateDB, method, args) + case EditValidatorMethod: + return p.EditValidator(ctx, contract, stateDB, method, args) + case DelegateMethod: + return p.Delegate(ctx, contract, stateDB, method, args) + case UndelegateMethod: + return p.Undelegate(ctx, contract, stateDB, method, args) + case RedelegateMethod: + return p.Redelegate(ctx, contract, stateDB, method, args) + case CancelUnbondingDelegationMethod: + return p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) + // Staking queries + case DelegationMethod: + return p.Delegation(ctx, contract, method, args) + case UnbondingDelegationMethod: + return p.UnbondingDelegation(ctx, contract, method, args) + case ValidatorMethod: + return p.Validator(ctx, method, contract, args) + case ValidatorsMethod: + return p.Validators(ctx, method, contract, args) + case RedelegationMethod: + return p.Redelegation(ctx, method, contract, args) + case RedelegationsMethod: + return p.Redelegations(ctx, method, contract, args) + } + return nil, nil + }, + ) } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/werc20/werc20.go b/precompiles/werc20/werc20.go index 74baebb94..b0fe36e7c 100644 --- a/precompiles/werc20/werc20.go +++ b/precompiles/werc20/werc20.go @@ -3,9 +3,9 @@ package werc20 import ( "embed" "fmt" - "slices" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/evm/x/vm/statedb" + "slices" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -108,22 +108,22 @@ func (p Precompile) RequiredGas(input []byte) uint64 { // Run executes the precompiled contract WERC20 methods defined in the ABI. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.SetupAndRun(evm, contract, readOnly, p.IsTransaction, p.HandleMethod) -} - -// HandleMethod handles the execution of each method -func (p Precompile) HandleMethod(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch { - case method.Type == abi.Fallback, - method.Type == abi.Receive, - method.Name == DepositMethod: - return p.Deposit(ctx, contract, stateDB) - case method.Name == WithdrawMethod: - return p.Withdraw(ctx, contract, stateDB, args) - default: - // ERC20 transactions and queries - return p.HandleMethod(ctx, contract, stateDB, method, args) - } + return p.ExecuteWithBalanceHandling( + evm, contract, readOnly, p.IsTransaction, + func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { + switch { + case method.Type == abi.Fallback, + method.Type == abi.Receive, + method.Name == DepositMethod: + return p.Deposit(ctx, contract, stateDB) + case method.Name == WithdrawMethod: + return p.Withdraw(ctx, contract, stateDB, args) + default: + // ERC20 transactions and queries + return p.HandleMethod(ctx, contract, stateDB, method, args) + } + }, + ) } // IsTransaction returns true if the given method name correspond to a From b938988772ca3d1dac54853701966b515365e103 Mon Sep 17 00:00:00 2001 From: Kyuhyeon Choi Date: Thu, 3 Jul 2025 21:51:43 +0900 Subject: [PATCH 19/19] Revert "Merge pull request #1 from zsystm/poc/precompiles-balance-handler" This reverts commit 46cd52739da67aae2395840ea6f3311cc7d90a5e, reversing changes made to b532fd547d56ce6b3d82562f0be5133da3ca6bb3. --- precompiles/common/precompile.go | 38 -------- precompiles/distribution/distribution.go | 110 ++++++++++++++--------- precompiles/erc20/erc20.go | 40 +++++++-- precompiles/evidence/evidence.go | 62 +++++++++---- precompiles/gov/gov.go | 109 +++++++++++++--------- precompiles/ics20/ics20.go | 67 +++++++++----- precompiles/slashing/slashing.go | 62 +++++++++---- precompiles/staking/staking.go | 95 ++++++++++++-------- precompiles/werc20/werc20.go | 62 +++++++++---- 9 files changed, 398 insertions(+), 247 deletions(-) diff --git a/precompiles/common/precompile.go b/precompiles/common/precompile.go index e188dae8e..a78ecfbe8 100644 --- a/precompiles/common/precompile.go +++ b/precompiles/common/precompile.go @@ -231,41 +231,3 @@ func (p *Precompile) GetBalanceHandler() *BalanceHandler { } return p.balanceHandler } - -// ExecuteWithBalanceHandling wraps the execution of a precompile method with -// the necessary balance handling and gas accounting logic. Precompile -// implementations can use this helper to avoid repetitive boilerplate in their -// `Run` methods. -func (p Precompile) ExecuteWithBalanceHandling( - evm *vm.EVM, - contract *vm.Contract, - readOnly bool, - isTransaction func(*abi.Method) bool, - handle func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error), -) (bz []byte, err error) { - ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, isTransaction) - if err != nil { - return nil, err - } - - p.GetBalanceHandler().BeforeBalanceChange(ctx) - - defer HandleGasError(ctx, contract, initialGas, &err)() - - bz, err = handle(ctx, contract, stateDB, method, args) - if err != nil { - return nil, err - } - - cost := ctx.GasMeter().GasConsumed() - initialGas - - if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { - return nil, vm.ErrOutOfGas - } - - if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { - return nil, err - } - - return bz, nil -} diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index ee81249ec..a9bb42faa 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -3,11 +3,10 @@ package distribution import ( "embed" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -85,48 +84,71 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract distribution methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // Custom transactions - case ClaimRewardsMethod: - return p.ClaimRewards(ctx, contract, stateDB, method, args) - // Distribution transactions - case SetWithdrawAddressMethod: - return p.SetWithdrawAddress(ctx, contract, stateDB, method, args) - case WithdrawDelegatorRewardMethod: - return p.WithdrawDelegatorReward(ctx, contract, stateDB, method, args) - case WithdrawValidatorCommissionMethod: - return p.WithdrawValidatorCommission(ctx, contract, stateDB, method, args) - case FundCommunityPoolMethod: - return p.FundCommunityPool(ctx, contract, stateDB, method, args) - case DepositValidatorRewardsPoolMethod: - return p.DepositValidatorRewardsPool(ctx, contract, stateDB, method, args) - // Distribution queries - case ValidatorDistributionInfoMethod: - return p.ValidatorDistributionInfo(ctx, contract, method, args) - case ValidatorOutstandingRewardsMethod: - return p.ValidatorOutstandingRewards(ctx, contract, method, args) - case ValidatorCommissionMethod: - return p.ValidatorCommission(ctx, contract, method, args) - case ValidatorSlashesMethod: - return p.ValidatorSlashes(ctx, contract, method, args) - case DelegationRewardsMethod: - return p.DelegationRewards(ctx, contract, method, args) - case DelegationTotalRewardsMethod: - return p.DelegationTotalRewards(ctx, contract, method, args) - case DelegatorValidatorsMethod: - return p.DelegatorValidators(ctx, contract, method, args) - case DelegatorWithdrawAddressMethod: - return p.DelegatorWithdrawAddress(ctx, contract, method, args) - case CommunityPoolMethod: - return p.CommunityPool(ctx, contract, method, args) - } - return nil, nil - }, - ) +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. + // It avoids panics and returns the out of gas error so the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + switch method.Name { + // Custom transactions + case ClaimRewardsMethod: + bz, err = p.ClaimRewards(ctx, contract, stateDB, method, args) + // Distribution transactions + case SetWithdrawAddressMethod: + bz, err = p.SetWithdrawAddress(ctx, contract, stateDB, method, args) + case WithdrawDelegatorRewardMethod: + bz, err = p.WithdrawDelegatorReward(ctx, contract, stateDB, method, args) + case WithdrawValidatorCommissionMethod: + bz, err = p.WithdrawValidatorCommission(ctx, contract, stateDB, method, args) + case FundCommunityPoolMethod: + bz, err = p.FundCommunityPool(ctx, contract, stateDB, method, args) + case DepositValidatorRewardsPoolMethod: + bz, err = p.DepositValidatorRewardsPool(ctx, contract, stateDB, method, args) + // Distribution queries + case ValidatorDistributionInfoMethod: + bz, err = p.ValidatorDistributionInfo(ctx, contract, method, args) + case ValidatorOutstandingRewardsMethod: + bz, err = p.ValidatorOutstandingRewards(ctx, contract, method, args) + case ValidatorCommissionMethod: + bz, err = p.ValidatorCommission(ctx, contract, method, args) + case ValidatorSlashesMethod: + bz, err = p.ValidatorSlashes(ctx, contract, method, args) + case DelegationRewardsMethod: + bz, err = p.DelegationRewards(ctx, contract, method, args) + case DelegationTotalRewardsMethod: + bz, err = p.DelegationTotalRewards(ctx, contract, method, args) + case DelegatorValidatorsMethod: + bz, err = p.DelegatorValidators(ctx, contract, method, args) + case DelegatorWithdrawAddressMethod: + bz, err = p.DelegatorWithdrawAddress(ctx, contract, method, args) + case CommunityPoolMethod: + bz, err = p.CommunityPool(ctx, contract, method, args) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + return bz, nil } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/erc20/erc20.go b/precompiles/erc20/erc20.go index b8ccf1986..cc3f9176a 100644 --- a/precompiles/erc20/erc20.go +++ b/precompiles/erc20/erc20.go @@ -3,9 +3,9 @@ package erc20 import ( "embed" "fmt" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -135,7 +135,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract ERC-20 methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { // ERC20 precompiles cannot receive funds because they are not managed by an // EOA and will not be possible to recover funds sent to an instance of // them.This check is a safety measure because currently funds cannot be @@ -144,12 +144,36 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]by return nil, fmt.Errorf(ErrCannotReceiveFunds, contract.Value().String()) } - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - return p.HandleMethod(ctx, contract, stateDB, method, args) - }, - ) + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. + // It avoids panics and returns the out of gas error so the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + bz, err = p.HandleMethod(ctx, contract, stateDB, method, args) + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB) + if err != nil { + return nil, err + } + + return bz, nil } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/evidence/evidence.go b/precompiles/evidence/evidence.go index d4d916d13..d9db833b0 100644 --- a/precompiles/evidence/evidence.go +++ b/precompiles/evidence/evidence.go @@ -3,10 +3,10 @@ package evidence import ( "embed" "fmt" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -81,24 +81,48 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract evidence methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // evidence transactions - case SubmitEvidenceMethod: - return p.SubmitEvidence(ctx, contract, stateDB, method, args) - // evidence queries - case EvidenceMethod: - return p.Evidence(ctx, method, args) - case GetAllEvidenceMethod: - return p.GetAllEvidence(ctx, method, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - }, - ) +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. + // It avoids panics and returns the out of gas error so the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + switch method.Name { + // evidence transactions + case SubmitEvidenceMethod: + bz, err = p.SubmitEvidence(ctx, contract, stateDB, method, args) + // evidence queries + case EvidenceMethod: + bz, err = p.Evidence(ctx, method, args) + case GetAllEvidenceMethod: + bz, err = p.GetAllEvidence(ctx, method, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + return bz, nil } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index 43171b533..447fab588 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -3,10 +3,10 @@ package gov import ( "embed" "fmt" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -85,47 +85,72 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract gov methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // gov transactions - case VoteMethod: - return p.Vote(ctx, contract, stateDB, method, args) - case VoteWeightedMethod: - return p.VoteWeighted(ctx, contract, stateDB, method, args) - case SubmitProposalMethod: - return p.SubmitProposal(ctx, contract, stateDB, method, args) - case DepositMethod: - return p.Deposit(ctx, contract, stateDB, method, args) - case CancelProposalMethod: - return p.CancelProposal(ctx, contract, stateDB, method, args) - - // gov queries - case GetVoteMethod: - return p.GetVote(ctx, method, contract, args) - case GetVotesMethod: - return p.GetVotes(ctx, method, contract, args) - case GetDepositMethod: - return p.GetDeposit(ctx, method, contract, args) - case GetDepositsMethod: - return p.GetDeposits(ctx, method, contract, args) - case GetTallyResultMethod: - return p.GetTallyResult(ctx, method, contract, args) - case GetProposalMethod: - return p.GetProposal(ctx, method, contract, args) - case GetProposalsMethod: - return p.GetProposals(ctx, method, contract, args) - case GetParamsMethod: - return p.GetParams(ctx, method, contract, args) - case GetConstitutionMethod: - return p.GetConstitution(ctx, method, contract, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - }, - ) +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. + // It avoids panics and returns the out of gas error so the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + switch method.Name { + // gov transactions + case VoteMethod: + bz, err = p.Vote(ctx, contract, stateDB, method, args) + case VoteWeightedMethod: + bz, err = p.VoteWeighted(ctx, contract, stateDB, method, args) + case SubmitProposalMethod: + bz, err = p.SubmitProposal(ctx, contract, stateDB, method, args) + case DepositMethod: + bz, err = p.Deposit(ctx, contract, stateDB, method, args) + case CancelProposalMethod: + bz, err = p.CancelProposal(ctx, contract, stateDB, method, args) + + // gov queries + case GetVoteMethod: + bz, err = p.GetVote(ctx, method, contract, args) + case GetVotesMethod: + bz, err = p.GetVotes(ctx, method, contract, args) + case GetDepositMethod: + bz, err = p.GetDeposit(ctx, method, contract, args) + case GetDepositsMethod: + bz, err = p.GetDeposits(ctx, method, contract, args) + case GetTallyResultMethod: + bz, err = p.GetTallyResult(ctx, method, contract, args) + case GetProposalMethod: + bz, err = p.GetProposal(ctx, method, contract, args) + case GetProposalsMethod: + bz, err = p.GetProposals(ctx, method, contract, args) + case GetParamsMethod: + bz, err = p.GetParams(ctx, method, contract, args) + case GetConstitutionMethod: + bz, err = p.GetConstitution(ctx, method, contract, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB) + if err != nil { + return nil, err + } + + return bz, nil } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/ics20/ics20.go b/precompiles/ics20/ics20.go index 2bf7d70a0..06cb7dac7 100644 --- a/precompiles/ics20/ics20.go +++ b/precompiles/ics20/ics20.go @@ -3,11 +3,10 @@ package ics20 import ( "embed" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -92,26 +91,50 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract IBC transfer methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // ICS20 transactions - case TransferMethod: - return p.Transfer(ctx, contract, stateDB, method, args) - // ICS20 queries - case DenomMethod: - return p.Denom(ctx, contract, method, args) - case DenomsMethod: - return p.Denoms(ctx, contract, method, args) - case DenomHashMethod: - return p.DenomHash(ctx, contract, method, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - }, - ) +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. + // It avoids panics and returns the out of gas error so the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + switch method.Name { + // ICS20 transactions + case TransferMethod: + bz, err = p.Transfer(ctx, contract, stateDB, method, args) + // ICS20 queries + case DenomMethod: + bz, err = p.Denom(ctx, contract, method, args) + case DenomsMethod: + bz, err = p.Denoms(ctx, contract, method, args) + case DenomHashMethod: + bz, err = p.DenomHash(ctx, contract, method, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + return bz, nil } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/slashing/slashing.go b/precompiles/slashing/slashing.go index 73c953600..352bde020 100644 --- a/precompiles/slashing/slashing.go +++ b/precompiles/slashing/slashing.go @@ -3,10 +3,10 @@ package slashing import ( "embed" "fmt" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -81,24 +81,48 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract slashing methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // slashing transactions - case UnjailMethod: - return p.Unjail(ctx, method, stateDB, contract, args) - // slashing queries - case GetSigningInfoMethod: - return p.GetSigningInfo(ctx, method, contract, args) - case GetSigningInfosMethod: - return p.GetSigningInfos(ctx, method, contract, args) - default: - return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) - } - }, - ) +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. + // It avoids panics and returns the out of gas error so the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + switch method.Name { + // slashing transactions + case UnjailMethod: + bz, err = p.Unjail(ctx, method, stateDB, contract, args) + // slashing queries + case GetSigningInfoMethod: + bz, err = p.GetSigningInfo(ctx, method, contract, args) + case GetSigningInfosMethod: + bz, err = p.GetSigningInfos(ctx, method, contract, args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + if err := p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + return bz, nil } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 2a34e693a..56fa9e53f 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -2,10 +2,10 @@ package staking import ( "embed" - "github.com/cosmos/evm/x/vm/statedb" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -80,41 +80,64 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract staking methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch method.Name { - // Staking transactions - case CreateValidatorMethod: - return p.CreateValidator(ctx, contract, stateDB, method, args) - case EditValidatorMethod: - return p.EditValidator(ctx, contract, stateDB, method, args) - case DelegateMethod: - return p.Delegate(ctx, contract, stateDB, method, args) - case UndelegateMethod: - return p.Undelegate(ctx, contract, stateDB, method, args) - case RedelegateMethod: - return p.Redelegate(ctx, contract, stateDB, method, args) - case CancelUnbondingDelegationMethod: - return p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) - // Staking queries - case DelegationMethod: - return p.Delegation(ctx, contract, method, args) - case UnbondingDelegationMethod: - return p.UnbondingDelegation(ctx, contract, method, args) - case ValidatorMethod: - return p.Validator(ctx, method, contract, args) - case ValidatorsMethod: - return p.Validators(ctx, method, contract, args) - case RedelegationMethod: - return p.Redelegation(ctx, method, contract, args) - case RedelegationsMethod: - return p.Redelegations(ctx, method, contract, args) - } - return nil, nil - }, - ) +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. + // It avoids panics and returns the out of gas error so the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + switch method.Name { + // Staking transactions + case CreateValidatorMethod: + bz, err = p.CreateValidator(ctx, contract, stateDB, method, args) + case EditValidatorMethod: + bz, err = p.EditValidator(ctx, contract, stateDB, method, args) + case DelegateMethod: + bz, err = p.Delegate(ctx, contract, stateDB, method, args) + case UndelegateMethod: + bz, err = p.Undelegate(ctx, contract, stateDB, method, args) + case RedelegateMethod: + bz, err = p.Redelegate(ctx, contract, stateDB, method, args) + case CancelUnbondingDelegationMethod: + bz, err = p.CancelUnbondingDelegation(ctx, contract, stateDB, method, args) + // Staking queries + case DelegationMethod: + bz, err = p.Delegation(ctx, contract, method, args) + case UnbondingDelegationMethod: + bz, err = p.UnbondingDelegation(ctx, contract, method, args) + case ValidatorMethod: + bz, err = p.Validator(ctx, method, contract, args) + case ValidatorsMethod: + bz, err = p.Validators(ctx, method, contract, args) + case RedelegationMethod: + bz, err = p.Redelegation(ctx, method, contract, args) + case RedelegationsMethod: + bz, err = p.Redelegations(ctx, method, contract, args) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + if err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + return bz, nil } // IsTransaction checks if the given method name corresponds to a transaction or query. diff --git a/precompiles/werc20/werc20.go b/precompiles/werc20/werc20.go index b0fe36e7c..607eefe59 100644 --- a/precompiles/werc20/werc20.go +++ b/precompiles/werc20/werc20.go @@ -3,12 +3,11 @@ package werc20 import ( "embed" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/evm/x/vm/statedb" "slices" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" @@ -107,23 +106,48 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract WERC20 methods defined in the ABI. -func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { - return p.ExecuteWithBalanceHandling( - evm, contract, readOnly, p.IsTransaction, - func(ctx sdk.Context, contract *vm.Contract, stateDB *statedb.StateDB, method *abi.Method, args []interface{}) ([]byte, error) { - switch { - case method.Type == abi.Fallback, - method.Type == abi.Receive, - method.Name == DepositMethod: - return p.Deposit(ctx, contract, stateDB) - case method.Name == WithdrawMethod: - return p.Withdraw(ctx, contract, stateDB, args) - default: - // ERC20 transactions and queries - return p.HandleMethod(ctx, contract, stateDB, method, args) - } - }, - ) +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // Start the balance change handler before executing the precompile. + p.GetBalanceHandler().BeforeBalanceChange(ctx) + + // This handles any out of gas errors that may occur during the execution of + // a precompile tx or query. It avoids panics and returns the out of gas error so + // the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + switch { + case method.Type == abi.Fallback, + method.Type == abi.Receive, + method.Name == DepositMethod: + bz, err = p.Deposit(ctx, contract, stateDB) + case method.Name == WithdrawMethod: + bz, err = p.Withdraw(ctx, contract, stateDB, args) + default: + // ERC20 transactions and queries + bz, err = p.HandleMethod(ctx, contract, stateDB, method, args) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + if err := p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB); err != nil { + return nil, err + } + + return bz, nil } // IsTransaction returns true if the given method name correspond to a