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()