diff --git a/consensus/wbft/backend/backend.go b/consensus/wbft/backend/backend.go index 01c4bd64b..a6de34bac 100644 --- a/consensus/wbft/backend/backend.go +++ b/consensus/wbft/backend/backend.go @@ -302,8 +302,8 @@ func (sb *Backend) CheckSignature(data []byte, address common.Address, sig []byt return nil } -// HasPropsal implements wbft.Backend.HashBlock -func (sb *Backend) HasPropsal(hash common.Hash, number *big.Int) bool { +// HasProposal implements wbft.Backend.HashBlock +func (sb *Backend) HasProposal(hash common.Hash, number *big.Int) bool { return sb.chain.GetHeader(hash, number.Uint64()) != nil } diff --git a/consensus/wbft/common/errors.go b/consensus/wbft/common/errors.go index e495ac906..a709efc0f 100644 --- a/consensus/wbft/common/errors.go +++ b/consensus/wbft/common/errors.go @@ -143,4 +143,9 @@ var ( ErrIsNotWBFTBlock = errors.New("block is not a wbft block") ErrEpochInfoIsNotNil = errors.New("epoch info should be nil for non-epoch block") + + ErrStateUnavailable = errors.New("state unavailable for verification") + + // ErrBlacklistedSigner is returned when a block is signed by a blacklisted account. + ErrBlacklistedSigner = errors.New("blacklisted signer") ) diff --git a/consensus/wbft/core/types.go b/consensus/wbft/core/types.go index ed8f66cec..8db08309c 100644 --- a/consensus/wbft/core/types.go +++ b/consensus/wbft/core/types.go @@ -127,8 +127,8 @@ type Backend interface { // LastProposal retrieves latest committed proposal and the address of proposer LastProposal() (wbft.Proposal, common.Address) - // HasPropsal checks if the combination of the given hash and height matches any existing blocks - HasPropsal(hash common.Hash, number *big.Int) bool + // HasProposal checks if the combination of the given hash and height matches any existing blocks + HasProposal(hash common.Hash, number *big.Int) bool // GetProposer returns the proposer of the given block height GetProposer(number uint64) common.Address diff --git a/consensus/wbft/engine/engine.go b/consensus/wbft/engine/engine.go index 38c6cbc63..dac646594 100644 --- a/consensus/wbft/engine/engine.go +++ b/consensus/wbft/engine/engine.go @@ -287,8 +287,13 @@ func (e *Engine) verifyCascadingFields(chain consensus.ChainHeaderReader, header } // Verify signer - if err := e.verifySigner(chain, header, parents, validators); err != nil { - return err + if err := e.verifySigner(chain, header, parent, validators); err != nil { + if !errors.Is(err, wbftcommon.ErrStateUnavailable) { + return err + } + // Skip blacklisted signer verification when state is not yet available. + // For more details, refer to the comment in verifyGasTip. + log.Trace("WBFT: Skipping blacklisted signer verification due to unavailable state", "number", header.Number, "err", err) } // extract the extra data from the header @@ -343,7 +348,7 @@ func (e *Engine) verifyCascadingFields(chain consensus.ChainHeaderReader, header return nil } -func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header, validators wbft.ValidatorSet) error { +func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header, validators wbft.ValidatorSet) error { // Verifying the genesis block is not supported number := header.Number.Uint64() if number == 0 { @@ -361,6 +366,16 @@ func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.H return wbftcommon.ErrUnauthorized } + // The caller ensures that parent is non-nil. + // It is already validated in the calling context. + state, err := chain.StateAt(parent.Root) + if err != nil { + return fmt.Errorf("%w: %v", wbftcommon.ErrStateUnavailable, err) + } + if state.IsBlacklisted(signer) { + return fmt.Errorf("%w: %s", wbftcommon.ErrBlacklistedSigner, signer.Hex()) + } + return nil } @@ -455,7 +470,12 @@ func (e *Engine) VerifySeal(chain consensus.ChainHeaderReader, header *types.Hea return wbftcommon.ErrInvalidDifficulty } - return e.verifySigner(chain, header, nil, validators) + parent := chain.GetHeader(header.ParentHash, number-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + + return e.verifySigner(chain, header, parent, validators) } func (e *Engine) PeriodToNextBlock(blockNumber *big.Int) uint64 { diff --git a/consensus/wbft/engine/engine_test.go b/consensus/wbft/engine/engine_test.go index c40b6f8fe..aadbdcb45 100644 --- a/consensus/wbft/engine/engine_test.go +++ b/consensus/wbft/engine/engine_test.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/wbft" wbftcommon "github.com/ethereum/go-ethereum/consensus/wbft/common" wbftcore "github.com/ethereum/go-ethereum/consensus/wbft/core" + "github.com/ethereum/go-ethereum/consensus/wbft/validator" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -45,6 +46,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/systemcontracts" + "github.com/stretchr/testify/require" ) // RLP data can be generated by using TestGeneratingGenesisExtra in testutils package @@ -442,6 +444,7 @@ func TestWriteRandao(t *testing.T) { type fakeChain struct { chainConfig *params.ChainConfig headers []*types.Header + statedb *state.StateDB } var _ consensus.ChainHeaderReader = (*fakeChain)(nil) @@ -456,7 +459,7 @@ func (f *fakeChain) GetHeader(hash common.Hash, number uint64) *types.Header { func (f *fakeChain) GetHeaderByNumber(number uint64) *types.Header { return nil } func (f *fakeChain) GetHeaderByHash(hash common.Hash) *types.Header { return nil } func (f *fakeChain) GetTd(hash common.Hash, number uint64) *big.Int { return nil } -func (f *fakeChain) StateAt(root common.Hash) (*state.StateDB, error) { return nil, nil } +func (f *fakeChain) StateAt(root common.Hash) (*state.StateDB, error) { return f.statedb, nil } func (f *fakeChain) insertHeader(h *types.Header) { f.headers = append(f.headers, h) @@ -882,3 +885,75 @@ func TestComputeShuffledIndex(t *testing.T) { t.Errorf("expected results to be equal, but got different results: %v and %v", result1, result2) } } + +func TestBlacklistedSigner(t *testing.T) { + signer := testAccount1 + + tests := []struct { + name string + blacklisted bool + expectErr error + errContainsPart string + }{ + { + name: "not blacklisted signer", + blacklisted: false, + expectErr: nil, + }, + { + name: "blacklisted signer", + blacklisted: true, + expectErr: wbftcommon.ErrBlacklistedSigner, + errContainsPart: signer.addr.Hex(), + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + parent := &types.Header{ + Number: big.NewInt(1), + Root: common.Hash{}, + } + + mockState, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + mockState.CreateAccount(signer.addr) + if tc.blacklisted { + mockState.SetBlacklisted(signer.addr) + } + + fc := &fakeChain{ + chainConfig: params.TestWBFTChainConfig, + headers: []*types.Header{nil, parent}, + statedb: mockState, + } + + engine := NewEngine(nil, common.Address{}, nil, nil) + + header := &types.Header{ + Number: big.NewInt(2), + ParentHash: common.HexToHash("0x1"), + Coinbase: signer.addr, + } + + addrs := []common.Address{ + signer.addr, + } + blsPubKeys := [][]byte{ + signer.blsKey.PublicKey().Marshal(), + } + validators := validator.NewSet(addrs, blsPubKeys, wbft.NewRoundRobinProposerPolicy()) + + err := engine.verifySigner(fc, header, parent, validators) + + if tc.expectErr == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expectErr) + if tc.errContainsPart != "" { + require.Contains(t, err.Error(), tc.errContainsPart) + } + } + }) + } +} diff --git a/core/error.go b/core/error.go index ed326f63d..fe9fdb452 100644 --- a/core/error.go +++ b/core/error.go @@ -18,7 +18,9 @@ package core import ( "errors" + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -101,9 +103,6 @@ var ( // ErrSenderNoEOA is returned if the sender of a transaction is a contract. ErrSenderNoEOA = errors.New("sender not an eoa") - // ErrBlacklistedAccount is returned if the sender or recipient is blacklisted. - ErrBlacklistedAccount = errors.New("account is blacklisted") - // ErrBlobFeeCapTooLow is returned if the transaction fee cap is less than the // blob gas fee of the block. ErrBlobFeeCapTooLow = errors.New("max fee per blob gas less than block blob gas fee") @@ -114,3 +113,11 @@ var ( // ErrBlobTxCreate is returned if a blob transaction has no explicit to field. ErrBlobTxCreate = errors.New("blob transaction of type create") ) + +type ErrBlacklistedAccount struct { + Address common.Address +} + +func (e *ErrBlacklistedAccount) Error() string { + return fmt.Sprintf("blacklisted account: %s", e.Address.Hex()) +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 794bbe928..13dbb0307 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1358,11 +1358,14 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er // - Add precompiles to access list (2929) // - Add the contents of the optional tx access list (2930) // +// Anzeon fork: +// - Add native managers to access list +// // Potential EIPs: // - Reset access list (Berlin) // - Add coinbase to access list (EIP-3651) // - Reset transient storage (EIP-1153) -func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { +func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles, nativeManagers []common.Address, list types.AccessList) { if rules.IsBerlin { // Clear out any leftover from previous executions al := newAccessList() @@ -1385,9 +1388,8 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d if rules.IsShanghai { // EIP-3651: warm coinbase al.AddAddress(coinbase) } - - if rules.IsAnzeon { - al.AddAddress(params.NativeCoinManagerAddress) + for _, addr := range nativeManagers { + al.AddAddress(addr) } } // Reset transient storage at the beginning of transaction execution diff --git a/core/state_transition.go b/core/state_transition.go index 3e71e4960..d644ee44a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -478,19 +478,19 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if rules.IsAnzeon { // Check sender blacklist if st.state.IsBlacklisted(msg.From) { - return nil, fmt.Errorf("%w: sender %v", ErrBlacklistedAccount, msg.From.Hex()) + return nil, &ErrBlacklistedAccount{Address: msg.From} } // Check recipient blacklist (only for transfers, not for contract creation) if msg.To != nil && st.state.IsBlacklisted(*msg.To) { - return nil, fmt.Errorf("%w: recipient %v", ErrBlacklistedAccount, msg.To.Hex()) + return nil, &ErrBlacklistedAccount{Address: *msg.To} } } // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), vm.ActiveNativeManagers(rules), msg.AccessList) var ( ret []byte @@ -533,6 +533,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // fee delegation if st.msg.FeePayer != nil { payer = *st.msg.FeePayer + if rules.IsAnzeon && st.state.IsBlacklisted(payer) { + return nil, &ErrBlacklistedAccount{Address: payer} + } } st.evm.AddTransferLog(payer, st.evm.Context.Coinbase, fee) } diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 48631dfd8..78f6801ed 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1098,6 +1098,8 @@ func (p *BlobPool) validateTx(tx *types.Transaction) error { } // Ensure the transaction adheres to the stateful pool filters (nonce, balance) stateOpts := &txpool.ValidationOptionsWithState{ + Config: p.chain.Config(), + State: p.state, FirstNonceGap: func(addr common.Address) uint64 { diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index a1f67227a..7c0a06b14 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -633,6 +633,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro // rules and adheres to some heuristic limits of the local node (price and size). func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error { opts := &txpool.ValidationOptionsWithState{ + Config: pool.chainconfig, + State: pool.currentState, FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 426432168..a0fea986a 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -186,6 +186,8 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err // ValidationOptionsWithState define certain differences between stateful transaction // validation across the different pools without having to duplicate those checks. type ValidationOptionsWithState struct { + Config *params.ChainConfig // Chain configuration to selectively validate based on current fork rules + State *state.StateDB // State database to check nonces and balances against // FirstNonceGap is an optional callback to retrieve the first nonce gap in @@ -220,6 +222,15 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op log.Error("Transaction sender recovery failed", "err", err) return err } + // Ensure that neither the sender nor the recipient is blacklisted + if opts.Config.AnzeonEnabled() { + if opts.State.IsBlacklisted(from) { + return &core.ErrBlacklistedAccount{Address: from} + } + if to := tx.To(); to != nil && opts.State.IsBlacklisted(*to) { + return &core.ErrBlacklistedAccount{Address: *to} + } + } next := opts.State.GetNonce(from) if next > tx.Nonce() { return fmt.Errorf("%w: next nonce %v, tx nonce %v", core.ErrNonceTooLow, next, tx.Nonce()) @@ -233,10 +244,15 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op } // Ensure the transactor has enough funds to cover the transaction costs if tx.Type() == types.FeeDelegateDynamicFeeTxType { + feePayer := tx.FeePayer() + // Ensure that the fee payer is not blacklisted + if opts.Config.AnzeonEnabled() && opts.State.IsBlacklisted(*feePayer) { + return &core.ErrBlacklistedAccount{Address: *feePayer} + } if opts.State.GetBalance(from).ToBig().Cmp(tx.Value()) < 0 { return ErrSenderInsufficientFunds } - if opts.State.GetBalance(*tx.FeePayer()).ToBig().Cmp(tx.FeeCost()) < 0 { + if opts.State.GetBalance(*feePayer).ToBig().Cmp(tx.FeeCost()) < 0 { return ErrFeePayerInsufficientFunds } } else { diff --git a/core/txpool/validation_test.go b/core/txpool/validation_test.go new file mode 100644 index 000000000..599bda4ea --- /dev/null +++ b/core/txpool/validation_test.go @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2025 The go-stablenet Authors +// This file is part of the go-stablenet library. +// +// The go-stablenet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-stablenet library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-stablenet library. If not, see . + +package txpool + +import ( + "crypto/ecdsa" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +type BlacklistRole uint8 + +const ( + NoneRole BlacklistRole = iota + SenderRole + RecipientRole + FeePayerRole +) + +type account struct { + privKey *ecdsa.PrivateKey + address common.Address +} + +var ( + chainConfig *params.ChainConfig + signer types.Signer + feeDelegateSigner types.Signer + initialBalance *uint256.Int +) + +func init() { + chainConfig = params.TestWBFTChainConfig + signer = types.LatestSigner(chainConfig) + feeDelegateSigner = types.NewFeeDelegateSigner(chainConfig.ChainID) + initialBalance = uint256.MustFromDecimal("1000000000000000000000") +} + +func newStateDB() *state.StateDB { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + return statedb +} + +func newOptions(statedb *state.StateDB) *ValidationOptionsWithState { + opts := &ValidationOptionsWithState{ + Config: params.TestWBFTChainConfig, + State: statedb, + FirstNonceGap: nil, + UsedAndLeftSlots: nil, + ExistingExpenditure: nil, + ExistingCost: nil, + } + return opts +} + +func newTestState() (*state.StateDB, *ValidationOptionsWithState) { + statedb := newStateDB() + return statedb, newOptions(statedb) +} + +func newAccount(statedb *state.StateDB) *account { + key, _ := crypto.GenerateKey() + address := crypto.PubkeyToAddress(key.PublicKey) + + account := &account{key, address} + statedb.CreateAccount(account.address) + statedb.AddBalance(account.address, initialBalance) + + return account +} + +func newDynamicFeeTx(to *common.Address) types.DynamicFeeTx { + return types.DynamicFeeTx{ + ChainID: chainConfig.ChainID, + Nonce: 0, + GasTipCap: new(big.Int).SetUint64(params.InitialGasTip), + GasFeeCap: new(big.Int).Add(new(big.Int).SetUint64(params.MinBaseFee), new(big.Int).SetUint64(params.InitialGasTip)), + Gas: params.TxGas, + To: to, + Value: big.NewInt(1000000000000000000), + Data: nil, + AccessList: nil, + } +} + +func signDynamicFeeTx(tx types.DynamicFeeTx, sender *account) (*types.Transaction, error) { + return types.SignNewTx(sender.privKey, signer, &tx) +} + +func signFeeDelegateTx(rawTx *types.Transaction, feePayer *account) (*types.Transaction, error) { + V, R, S := rawTx.RawSignatureValues() + senderTx := types.DynamicFeeTx{ + To: rawTx.To(), + ChainID: rawTx.ChainId(), + Nonce: rawTx.Nonce(), + Gas: rawTx.Gas(), + GasFeeCap: rawTx.GasFeeCap(), + GasTipCap: rawTx.GasTipCap(), + Value: rawTx.Value(), + Data: rawTx.Data(), + AccessList: rawTx.AccessList(), + V: V, + R: R, + S: S, + } + + feeDelegateTx := &types.FeeDelegateDynamicFeeTx{ + FeePayer: &feePayer.address, + } + feeDelegateTx.SetSenderTx(senderTx) + tx := types.NewTx(feeDelegateTx) + + return types.SignTx(tx, feeDelegateSigner, feePayer.privKey) +} + +func TestBlacklistedAccountTx(t *testing.T) { + t.Run("DynamicFeeTx", func(t *testing.T) { + tests := []struct { + name string + blacklistedRole BlacklistRole + expectErr bool + }{ + { + name: "unrelated to any blacklisted account", + blacklistedRole: NoneRole, + expectErr: false, + }, + { + name: "sender is blacklisted", + blacklistedRole: SenderRole, + expectErr: true, + }, + { + name: "recipient is blacklisted", + blacklistedRole: RecipientRole, + expectErr: true, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + statedb, opts := newTestState() + testAccts := map[BlacklistRole]*account{ + SenderRole: newAccount(statedb), + RecipientRole: newAccount(statedb), + } + + if tc.blacklistedRole != NoneRole { + blacklistedAcct := testAccts[tc.blacklistedRole] + statedb.SetBlacklisted(blacklistedAcct.address) + } + + sender := testAccts[SenderRole] + recipient := testAccts[RecipientRole] + + tx := newDynamicFeeTx(&recipient.address) + signedTx, _ := signDynamicFeeTx(tx, sender) + + err := ValidateTransactionWithState(signedTx, signer, opts) + + if tc.expectErr { + require.Error(t, err) + + var haveErr *core.ErrBlacklistedAccount + require.ErrorAs(t, err, &haveErr) + require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) + } else { + require.NoError(t, err) + } + }) + } + }) + + t.Run("FeeDelegateDynamicFeeTx", func(t *testing.T) { + tests := []struct { + name string + blacklistedRole BlacklistRole + expectErr bool + }{ + { + name: "unrelated to any blacklisted account", + blacklistedRole: NoneRole, + expectErr: false, + }, + { + name: "sender is blacklisted", + blacklistedRole: SenderRole, + expectErr: true, + }, + { + name: "recipient is blacklisted", + blacklistedRole: RecipientRole, + expectErr: true, + }, + { + name: "feePayer is blacklisted", + blacklistedRole: FeePayerRole, + expectErr: true, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + statedb, opts := newTestState() + testAccts := map[BlacklistRole]*account{ + SenderRole: newAccount(statedb), + RecipientRole: newAccount(statedb), + FeePayerRole: newAccount(statedb), + } + + if tc.blacklistedRole != NoneRole { + blacklistedAcct := testAccts[tc.blacklistedRole] + statedb.SetBlacklisted(blacklistedAcct.address) + } + + sender := testAccts[SenderRole] + recipient := testAccts[RecipientRole] + feePayer := testAccts[FeePayerRole] + + tx := newDynamicFeeTx(&recipient.address) + signedTx, _ := signDynamicFeeTx(tx, sender) + feePayerSignedTx, _ := signFeeDelegateTx(signedTx, feePayer) + + err := ValidateTransactionWithState(feePayerSignedTx, signer, opts) + + if tc.expectErr { + require.Error(t, err) + + var haveErr *core.ErrBlacklistedAccount + require.ErrorAs(t, err, &haveErr) + require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) + } else { + require.NoError(t, err) + } + }) + } + }) +} diff --git a/core/vm/errors.go b/core/vm/errors.go index b2d93195b..f8948d9bf 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -19,6 +19,8 @@ package vm import ( "errors" "fmt" + + "github.com/ethereum/go-ethereum/common" ) // List evm execution errors @@ -77,3 +79,11 @@ type ErrInvalidOpCode struct { } func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } + +type ErrBlacklistedAccount struct { + Address common.Address +} + +func (e *ErrBlacklistedAccount) Error() string { + return fmt.Sprintf("blacklisted account: %s", e.Address.Hex()) +} diff --git a/core/vm/evm.go b/core/vm/evm.go index bc592280e..5a4e2166d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -195,6 +195,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + if err := evm.checkBlacklisted(caller.Address(), addr); err != nil { + return nil, gas, err + } m, isNativeManager := evm.nativeManager(addr) p, isPrecompile := evm.precompile(addr) if !value.IsZero() { @@ -299,6 +302,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + if err := evm.checkBlacklisted(caller.Address(), addr); err != nil { + return nil, gas, err + } // Fail if we're trying to transfer more than the available balance // Note although it's noop to transfer X ether to caller itself. But // if caller doesn't have enough balance, it would be an error to allow @@ -349,6 +355,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + if err := evm.checkBlacklisted(caller.Address(), addr); err != nil { + return nil, gas, err + } var snapshot = evm.StateDB.Snapshot() // Invoke tracer hooks that signal entering/exiting a call frame @@ -394,6 +403,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + if err := evm.checkBlacklisted(caller.Address(), addr); err != nil { + return nil, gas, err + } // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced // after all empty accounts were deleted, so this is not required. However, if we omit this, @@ -462,6 +474,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.depth > int(params.CallCreateDepth) { return nil, common.Address{}, gas, ErrDepth } + // Fail if the caller is blacklisted under Anzeon rules + if evm.chainRules.IsAnzeon && evm.StateDB.IsBlacklisted(caller.Address()) { + return nil, common.Address{}, gas, &ErrBlacklistedAccount{Address: caller.Address()} + } if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance } @@ -591,3 +607,18 @@ func (evm *EVM) AddTransferLog(sender, recipient common.Address, amount *uint256 BlockNumber: evm.Context.BlockNumber.Uint64(), }) } + +// Fail if from or to address is blacklisted under Anzeon rules +func (evm *EVM) checkBlacklisted(from common.Address, to common.Address) error { + if !evm.chainRules.IsAnzeon { + return nil + } + + if evm.StateDB.IsBlacklisted(from) { + return &ErrBlacklistedAccount{from} + } + if evm.StateDB.IsBlacklisted(to) { + return &ErrBlacklistedAccount{to} + } + return nil +} diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go new file mode 100644 index 000000000..3f79473a1 --- /dev/null +++ b/core/vm/evm_test.go @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2025 The go-stablenet Authors +// This file is part of the go-stablenet library. +// +// The go-stablenet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-stablenet library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-stablenet library. If not, see . + +package vm + +import ( + "crypto/ecdsa" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +const testGas = 100000 + +type BlacklistRole uint8 + +const ( + noneRole BlacklistRole = iota + callerRole + targetRole + contractRole + beneficiaryRole +) + +type account struct { + privKey *ecdsa.PrivateKey + address common.Address +} + +func newAccount(statedb StateDB) *account { + key, _ := crypto.GenerateKey() + address := crypto.PubkeyToAddress(key.PublicKey) + + account := &account{key, address} + statedb.CreateAccount(account.address) + statedb.AddBalance(account.address, uint256.NewInt(params.Ether)) + + return account +} + +func newStateDB() *state.StateDB { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + return statedb +} + +func newTestEvm(statedb StateDB) *EVM { + vmctx := BlockContext{ + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + CanTransfer: func(sd StateDB, a common.Address, i *uint256.Int) bool { return true }, + } + return NewEVM(vmctx, TxContext{}, statedb, params.TestWBFTChainConfig, Config{}) +} + +type callFn func(evm *EVM, caller *Contract, target common.Address) error + +func invokeCall(evm *EVM, caller *Contract, target common.Address) error { + _, _, err := evm.Call(caller, target, []byte{}, testGas, uint256.NewInt(0)) + return err +} + +func invokeDelegateCall(evm *EVM, caller *Contract, target common.Address) error { + _, _, err := evm.DelegateCall(caller, target, []byte{}, testGas) + return err +} + +func invokeCallCode(evm *EVM, caller *Contract, target common.Address) error { + _, _, err := evm.CallCode(caller, target, []byte{}, testGas, uint256.NewInt(0)) + return err +} + +func invokeStaticCall(evm *EVM, caller *Contract, target common.Address) error { + _, _, err := evm.StaticCall(caller, target, []byte{}, testGas) + return err +} + +func TestBlacklistedAccountExecution(t *testing.T) { + t.Run("Call", func(t *testing.T) { + tests := []struct { + name string + invoke callFn + blacklistedRole BlacklistRole + expectErr bool + }{ + { + name: "Call: unrelated to any blacklisted account", + invoke: invokeCall, + blacklistedRole: noneRole, + expectErr: false, + }, + { + name: "Call: caller is blacklisted", + invoke: invokeCall, + blacklistedRole: callerRole, + expectErr: true, + }, + { + name: "Call: target is blacklisted", + invoke: invokeCall, + blacklistedRole: targetRole, + expectErr: true, + }, + { + name: "DelegateCall: unrelated to any blacklisted account", + invoke: invokeDelegateCall, + blacklistedRole: noneRole, + expectErr: false, + }, + { + name: "DelegateCall: caller is blacklisted", + invoke: invokeDelegateCall, + blacklistedRole: callerRole, + expectErr: true, + }, + { + name: "DelegateCall: target is blacklisted", + invoke: invokeDelegateCall, + blacklistedRole: targetRole, + expectErr: true, + }, + { + name: "CallCode: unrelated to any blacklisted account", + invoke: invokeCallCode, + blacklistedRole: noneRole, + expectErr: false, + }, + { + name: "CallCode: caller is blacklisted", + invoke: invokeCallCode, + blacklistedRole: callerRole, + expectErr: true, + }, + { + name: "CallCode: target is blacklisted", + invoke: invokeCallCode, + blacklistedRole: targetRole, + expectErr: true, + }, + { + name: "StaticCall: unrelated to any blacklisted account", + invoke: invokeStaticCall, + blacklistedRole: noneRole, + expectErr: false, + }, + { + name: "StaticCall: caller is blacklisted", + invoke: invokeStaticCall, + blacklistedRole: callerRole, + expectErr: true, + }, + { + name: "StaticCall: target is blacklisted", + invoke: invokeStaticCall, + blacklistedRole: targetRole, + expectErr: true, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + statedb := newStateDB() + evm := newTestEvm(statedb) + + testAccts := map[BlacklistRole]*account{ + callerRole: newAccount(statedb), + targetRole: newAccount(statedb), + } + + if tc.blacklistedRole != noneRole { + blacklistedAcct := testAccts[tc.blacklistedRole] + statedb.SetBlacklisted(blacklistedAcct.address) + } + + caller := testAccts[callerRole] + target := testAccts[targetRole] + + callerRef := NewContract(AccountRef(caller.address), AccountRef(caller.address), uint256.NewInt(0), 0) + + err := tc.invoke(evm, callerRef, target.address) + if tc.expectErr { + require.Error(t, err) + + var haveErr *ErrBlacklistedAccount + require.ErrorAs(t, err, &haveErr) + require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) + } else { + require.NoError(t, err) + } + }) + } + }) + + t.Run("Create", func(t *testing.T) { + tests := []struct { + name string + blacklistedRole BlacklistRole + expectErr bool + }{ + { + name: "Create: unrelated to any blacklisted account", + blacklistedRole: noneRole, + expectErr: false, + }, + { + name: "Create: caller is blacklisted", + blacklistedRole: callerRole, + expectErr: true, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + statedb := newStateDB() + evm := newTestEvm(statedb) + + testAccts := map[BlacklistRole]*account{ + callerRole: newAccount(statedb), + } + + if tc.blacklistedRole != noneRole { + blacklistedAcct := testAccts[tc.blacklistedRole] + statedb.SetBlacklisted(blacklistedAcct.address) + } + + caller := testAccts[callerRole] + callerRef := AccountRef(caller.address) + + constructorCode := []byte{0x00} + codeAndHash := codeAndHash{ + code: constructorCode, + } + _, _, _, err := evm.create(callerRef, &codeAndHash, testGas, uint256.NewInt(0), common.Address{}, CREATE) + if tc.expectErr { + require.Error(t, err) + + var haveErr *ErrBlacklistedAccount + require.ErrorAs(t, err, &haveErr) + require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) + } else { + require.NoError(t, err) + } + }) + } + }) +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index b8055de6b..34fe801d9 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -801,9 +801,16 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext return nil, ErrWriteProtection } beneficiary := scope.Stack.pop() + if err := interpreter.evm.checkBlacklisted(scope.Contract.Address(), beneficiary.Bytes20()); err != nil { + return nil, err + } balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address()) + // Emit a transfer log if balance is non-zero + if !balance.IsZero() { + interpreter.evm.AddTransferLog(scope.Contract.Address(), beneficiary.Bytes20(), balance) + } if tracer := interpreter.evm.Config.Tracer; tracer != nil { tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) tracer.CaptureExit([]byte{}, 0, nil) @@ -816,9 +823,16 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon return nil, ErrWriteProtection } beneficiary := scope.Stack.pop() + if err := interpreter.evm.checkBlacklisted(scope.Contract.Address(), beneficiary.Bytes20()); err != nil { + return nil, err + } balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) + // Emit a transfer log if balance is non-zero + if !balance.IsZero() { + interpreter.evm.AddTransferLog(scope.Contract.Address(), beneficiary.Bytes20(), balance) + } interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address()) if tracer := interpreter.evm.Config.Tracer; tracer != nil { tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 8653864d1..444cd5e4c 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" + "github.com/stretchr/testify/require" ) type TwoOperandTestcase struct { @@ -643,7 +644,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { } } -func TestCreate2Addreses(t *testing.T) { +func TestCreate2Addresses(t *testing.T) { type testcase struct { origin string salt string @@ -928,3 +929,97 @@ func TestOpMCopy(t *testing.T) { } } } + +func TestBlacklistedAccountSelfdestruct(t *testing.T) { + tests := []struct { + name string + blacklistedRole BlacklistRole + instruction func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) + expectErr bool + }{ + { + name: "opSelfdestruct unrelated to any blacklisted account", + blacklistedRole: noneRole, + instruction: opSelfdestruct, + expectErr: false, + }, + { + name: "opSelfdestruct contract is blacklisted", + blacklistedRole: contractRole, + instruction: opSelfdestruct, + expectErr: true, + }, + { + name: "opSelfdestruct beneficiary is blacklisted", + blacklistedRole: beneficiaryRole, + instruction: opSelfdestruct, + expectErr: true, + }, + { + name: "opSelfdestruct6780 unrelated to any blacklisted account", + blacklistedRole: noneRole, + instruction: opSelfdestruct6780, + expectErr: false, + }, + { + name: "opSelfdestruct6780 contract is blacklisted", + blacklistedRole: contractRole, + instruction: opSelfdestruct6780, + expectErr: true, + }, + { + name: "opSelfdestruct6780 beneficiary is blacklisted", + blacklistedRole: beneficiaryRole, + instruction: opSelfdestruct6780, + expectErr: true, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + var ( + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + env = NewEVM(BlockContext{BlockNumber: new(big.Int).SetUint64(1)}, TxContext{}, statedb, params.TestWBFTChainConfig, Config{}) + stack = newstack() + mem = NewMemory() + pc = uint64(0) + evmInterpreter = env.interpreter + ) + + testAccts := map[BlacklistRole]*account{ + contractRole: newAccount(statedb), + beneficiaryRole: newAccount(statedb), + } + + if tc.blacklistedRole != noneRole { + blacklistedAcct := testAccts[tc.blacklistedRole] + statedb.SetBlacklisted(blacklistedAcct.address) + } + + contractAddr := testAccts[contractRole].address + beneficiaryAddr := testAccts[beneficiaryRole].address + + stack.push(new(uint256.Int).SetBytes(beneficiaryAddr.Bytes())) + contract := Contract{ + self: contractRef{addr: contractAddr}, + } + _, err := tc.instruction(&pc, evmInterpreter, &ScopeContext{mem, stack, &contract}) + if err == errStopToken { + err = nil + } + + if tc.expectErr { + require.Error(t, err) + + var haveErr *ErrBlacklistedAccount + require.ErrorAs(t, err, &haveErr) + require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/core/vm/interface.go b/core/vm/interface.go index 64fcac5ee..52bb163af 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -72,7 +72,7 @@ type StateDB interface { // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform // even if the feature/fork is not active yet AddSlotToAccessList(addr common.Address, slot common.Hash) - Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) + Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles, nativeManagers []common.Address, txAccesses types.AccessList) RevertToSnapshot(int) Snapshot() int diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 3513ed3a3..f6eb812a0 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -127,7 +127,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), vm.ActiveNativeManagers(rules), nil) cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. cfg.State.SetCode(address, code) @@ -160,7 +160,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), vm.ActiveNativeManagers(rules), nil) // Call the code with the given configuration. code, address, leftOverGas, err := vmenv.Create( sender, @@ -188,7 +188,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) + statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), vm.ActiveNativeManagers(rules), nil) // Call the code with the given configuration. ret, leftOverGas, err := vmenv.Call( diff --git a/systemcontracts/contracts.go b/systemcontracts/contracts.go index 6dce59330..164f47f46 100644 --- a/systemcontracts/contracts.go +++ b/systemcontracts/contracts.go @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright 2025 The go-stablenet Authors -// Copyright 2025 The go-stablenet Authors // This file is part of the go-stablenet library. // // The go-stablenet library is free software: you can redistribute it and/or modify diff --git a/systemcontracts/systemcontracts.go b/systemcontracts/systemcontracts.go index 3284ab490..678b05633 100644 --- a/systemcontracts/systemcontracts.go +++ b/systemcontracts/systemcontracts.go @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright 2025 The go-stablenet Authors -// Copyright 2025 The go-stablenet Authors // This file is part of the go-stablenet library. // // The go-stablenet library is free software: you can redistribute it and/or modify diff --git a/systemcontracts/test/coin_adapter_test.go b/systemcontracts/test/coin_adapter_test.go index 861f9177c..d2582d397 100644 --- a/systemcontracts/test/coin_adapter_test.go +++ b/systemcontracts/test/coin_adapter_test.go @@ -475,16 +475,16 @@ func TestNativeCoinAdapter(t *testing.T) { transferSig, r, s, v := g.BuildTransferWithAuthSig(t, from, to.Address, transferAmount, nil, nil, transferNonce) // 0 - MAX_UINT_256 // transfer by r,s,v { - balnaceFrom := g.BalanceOf(t, from.Address) - balnaceTo := g.BalanceOf(t, to.Address) + balanceFrom := g.BalanceOf(t, from.Address) + balanceTo := g.BalanceOf(t, to.Address) receipt, err := g.ExpectedOk( g.TransferWithAuthorization(t, minter1, from.Address, to.Address, transferAmount, nil, nil, transferNonce, v, r, s), ) require.NoError(t, err) - require.True(t, new(big.Int).Sub(balnaceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) - require.True(t, new(big.Int).Add(balnaceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) + require.True(t, new(big.Int).Sub(balanceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) + require.True(t, new(big.Int).Add(balanceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) // approval event approvalEvent := findEvent("Transfer", receipt.Logs) @@ -541,14 +541,14 @@ func TestNativeCoinAdapter(t *testing.T) { validBefore := new(big.Int).SetUint64(block.Time() + 100) transferSig, _, _, _ = g.BuildTransferWithAuthSig(t, from, to.Address, transferAmount, validAfter, validBefore, transferNonce) - balnaceFrom := g.BalanceOf(t, from.Address) - balnaceTo := g.BalanceOf(t, to.Address) + balanceFrom := g.BalanceOf(t, from.Address) + balanceTo := g.BalanceOf(t, to.Address) receipt, err := g.ExpectedOk(g.TransferWithAuthorization(t, minter1, from.Address, to.Address, transferAmount, validAfter, validBefore, transferNonce, transferSig)) require.NoError(t, err) - require.True(t, new(big.Int).Sub(balnaceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) - require.True(t, new(big.Int).Add(balnaceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) + require.True(t, new(big.Int).Sub(balanceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) + require.True(t, new(big.Int).Add(balanceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) // transfer event transferEvent := findEvent("Transfer", receipt.Logs) @@ -568,16 +568,16 @@ func TestNativeCoinAdapter(t *testing.T) { receiveSig, r, s, v := g.BuildReceiveWithAuthSig(t, from, to.Address, transferAmount, nil, nil, receiveNonce) // 0 - MAX_UINT_256 // receive by r,s,v { - balnaceFrom := g.BalanceOf(t, from.Address) - balnaceTo := g.BalanceOf(t, to.Address) + balanceFrom := g.BalanceOf(t, from.Address) + balanceTo := g.BalanceOf(t, to.Address) receipt, err := g.ExpectedOk( g.ReceiveWithAuthorization(t, to, from.Address, transferAmount, nil, nil, receiveNonce, v, r, s), ) require.NoError(t, err) - require.True(t, new(big.Int).Sub(balnaceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) - expectedBalance := new(big.Int).Sub(new(big.Int).Add(balnaceTo, transferAmount), calcGasCost(receipt)) + require.True(t, new(big.Int).Sub(balanceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) + expectedBalance := new(big.Int).Sub(new(big.Int).Add(balanceTo, transferAmount), calcGasCost(receipt)) require.True(t, expectedBalance.Cmp(g.BalanceOf(t, to.Address)) == 0) // approval event @@ -651,16 +651,16 @@ func TestNativeCoinAdapter(t *testing.T) { validBefore := new(big.Int).SetUint64(block.Time() + 100) receiveSig, _, _, _ = g.BuildReceiveWithAuthSig(t, from, to.Address, transferAmount, validAfter, validBefore, receiveNonce) - balnaceFrom := g.BalanceOf(t, from.Address) - balnaceTo := g.BalanceOf(t, to.Address) + balanceFrom := g.BalanceOf(t, from.Address) + balanceTo := g.BalanceOf(t, to.Address) receipt, err := g.ExpectedOk( g.ReceiveWithAuthorization(t, to, from.Address, transferAmount, validAfter, validBefore, receiveNonce, receiveSig), ) require.NoError(t, err) - require.True(t, new(big.Int).Sub(balnaceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) - expectedBalance := new(big.Int).Sub(new(big.Int).Add(balnaceTo, transferAmount), calcGasCost(receipt)) + require.True(t, new(big.Int).Sub(balanceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) + expectedBalance := new(big.Int).Sub(new(big.Int).Add(balanceTo, transferAmount), calcGasCost(receipt)) require.True(t, expectedBalance.Cmp(g.BalanceOf(t, to.Address)) == 0) // transfer event @@ -717,14 +717,14 @@ func TestNativeCoinAdapter(t *testing.T) { // transfer { - balnaceFrom := g.BalanceOf(t, from.Address) - balnaceTo := g.BalanceOf(t, to.Address) + balanceFrom := g.BalanceOf(t, from.Address) + balanceTo := g.BalanceOf(t, to.Address) receipt, err := g.ExpectedOk(g.TransferWithAuthorization(t, minter1, from.Address, to.Address, transferAmount, nil, nil, expectedFailNonce, transferSig)) require.NoError(t, err) - require.True(t, new(big.Int).Sub(balnaceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) - require.True(t, new(big.Int).Add(balnaceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) + require.True(t, new(big.Int).Sub(balanceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) + require.True(t, new(big.Int).Add(balanceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) // transfer event transferEvent := findEvent("Transfer", receipt.Logs) @@ -816,7 +816,8 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { normalAccount = NewEOA() blacklistedAccount = NewEOA() - expectedErrMsg = "account is blacklisted" + expectedRevertMsg = "account is blacklisted" // Revert triggered in contract(NativeCoinAdapter) + expectedErrMsg = "blacklisted account" // Error occurred in EVM ) councilMember := NewEOA() @@ -858,7 +859,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { beforeBalance := g.BalanceOf(t, blacklistedAccount.Address) // mint to blacklisted address - ExpectedRevert(t, g.ExpectedFail(g.Mint(t, minter, blacklistedAccount.Address, initialBalance)), expectedErrMsg) + ExpectedRevert(t, g.ExpectedFail(g.Mint(t, minter, blacklistedAccount.Address, initialBalance)), expectedRevertMsg) require.True(t, beforeBalance.Cmp(g.BalanceOf(t, blacklistedAccount.Address)) == 0) }) @@ -876,8 +877,8 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { } // transfer to blacklisted address - ExpectedRevert(t, g.ExpectedFail(g.Transfer(t, from, to.Address, new(big.Int))), expectedErrMsg) - ExpectedRevert(t, g.ExpectedFail(g.Transfer(t, from, to.Address, amount)), expectedErrMsg) + ExpectedRevert(t, g.ExpectedFail(g.Transfer(t, from, to.Address, new(big.Int))), expectedRevertMsg) + ExpectedRevert(t, g.ExpectedFail(g.Transfer(t, from, to.Address, amount)), expectedRevertMsg) // blacklisted address transfer ExpectedRevert(t, g.ExpectedFail(g.Transfer(t, to, from.Address, amount)), expectedErrMsg) @@ -903,14 +904,14 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { require.NoError(t, err) } // owner -> blacklist - ExpectedRevert(t, g.ExpectedFail(g.Approve(t, owner, spender.Address, approveAmount)), expectedErrMsg) + ExpectedRevert(t, g.ExpectedFail(g.Approve(t, owner, spender.Address, approveAmount)), expectedRevertMsg) // blacklist -> owner ExpectedRevert(t, g.ExpectedFail(g.Approve(t, spender, owner.Address, approveAmount)), expectedErrMsg) // transfer to blacklist - ExpectedRevert(t, g.ExpectedFail(g.TransferFrom(t, minter, owner.Address, spender.Address, transferAmount)), expectedErrMsg) + ExpectedRevert(t, g.ExpectedFail(g.TransferFrom(t, minter, owner.Address, spender.Address, transferAmount)), expectedRevertMsg) // transfer from blacklist - ExpectedRevert(t, g.ExpectedFail(g.TransferFrom(t, owner, spender.Address, minter.Address, new(big.Int))), expectedErrMsg) + ExpectedRevert(t, g.ExpectedFail(g.TransferFrom(t, owner, spender.Address, minter.Address, new(big.Int))), expectedRevertMsg) // msg.sender is blacklist ExpectedRevert(t, g.ExpectedFail(g.TransferFrom(t, spender, owner.Address, minter.Address, new(big.Int))), expectedErrMsg) }) @@ -926,16 +927,16 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { ExpectedRevert(t, g.ExpectedFail(g.Permit(t, minter, owner.Address, spender.Address, approveAmount, nil, permitSig)), - expectedErrMsg, + expectedRevertMsg, ) } - // onwer is blacklisted + // owner is blacklisted { permitSig, _, _, _ := g.BuildPermitSig(t, spender, owner.Address, approveAmount, nil) // deadline == MAX_UINT_256 ExpectedRevert(t, g.ExpectedFail(g.Permit(t, minter, spender.Address, owner.Address, approveAmount, nil, permitSig)), - expectedErrMsg, + expectedRevertMsg, ) } // msg.sender is blacklisted - should fail due to Go-level sender validation @@ -976,7 +977,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { ExpectedRevert(t, g.ExpectedFail(g.TransferWithAuthorization(t, minter, from.Address, to.Address, transferAmount, nil, nil, transferNonce, transferSig)), - expectedErrMsg, + expectedRevertMsg, ) } // transfer from blacklist @@ -986,7 +987,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { ExpectedRevert(t, g.ExpectedFail(g.TransferWithAuthorization(t, minter, to.Address, from.Address, transferAmount, nil, nil, transferNonce, transferSig)), - expectedErrMsg, + expectedRevertMsg, ) } // msg.sender is blacklisted - should fail due to Go-level sender validation @@ -1037,7 +1038,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { ExpectedRevert(t, g.ExpectedFail(g.ReceiveWithAuthorization(t, from, to.Address, transferAmount, nil, nil, receiveNonce, receiveSig)), - expectedErrMsg, + expectedRevertMsg, ) } // not blacklisted diff --git a/tests/state_test.go b/tests/state_test.go index 1d749d8bc..9281f89ae 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -301,7 +301,7 @@ func runBenchmark(b *testing.B, t *StateTest) { b.ResetTimer() for n := 0; n < b.N; n++ { snapshot := state.StateDB.Snapshot() - state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.ActivePrecompiles(rules), vm.ActiveNativeManagers(rules), msg.AccessList) b.StartTimer() start := time.Now()