diff --git a/node/node.go b/node/node.go index b2aba931443..7770033354d 100644 --- a/node/node.go +++ b/node/node.go @@ -75,6 +75,7 @@ import ( "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms" "github.com/ava-labs/avalanchego/vms/avm" + "github.com/ava-labs/avalanchego/vms/avm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/registry" @@ -1242,9 +1243,11 @@ func (n *Node) initVMs() error { }), n.VMManager.RegisterFactory(context.TODO(), constants.AVMID, &avm.Factory{ Config: avmconfig.Config{ - Upgrades: n.Config.UpgradeConfig, - TxFee: n.Config.StaticFeeConfig.TxFee, - CreateAssetTxFee: n.Config.CreateAssetTxFee, + StaticConfig: fee.StaticConfig{ + TxFee: n.Config.StaticFeeConfig.TxFee, + CreateAssetTxFee: n.Config.CreateAssetTxFee, + }, + Upgrades: n.Config.UpgradeConfig, }, }), n.VMManager.RegisterFactory(context.TODO(), constants.EVMID, &coreth.Factory{}), diff --git a/vms/avm/block/builder/builder_test.go b/vms/avm/block/builder/builder_test.go index 36159598b70..30cf6bb7749 100644 --- a/vms/avm/block/builder/builder_test.go +++ b/vms/avm/block/builder/builder_test.go @@ -20,11 +20,13 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/avm/block" + "github.com/ava-labs/avalanchego/vms/avm/config" "github.com/ava-labs/avalanchego/vms/avm/fxs" "github.com/ava-labs/avalanchego/vms/avm/metrics" "github.com/ava-labs/avalanchego/vms/avm/state" @@ -523,6 +525,11 @@ func TestBlockBuilderAddLocalTx(t *testing.T) { Ctx: &snow.Context{ Log: logging.NoLog{}, }, + Config: &config.Config{ + Upgrades: upgrade.Config{ + EtnaTime: mockable.MaxTime, + }, + }, Codec: parser.Codec(), } diff --git a/vms/avm/block/executor/block_test.go b/vms/avm/block/executor/block_test.go index c95796da638..2534f8c3170 100644 --- a/vms/avm/block/executor/block_test.go +++ b/vms/avm/block/executor/block_test.go @@ -26,6 +26,7 @@ import ( "github.com/ava-labs/avalanchego/vms/avm/state" "github.com/ava-labs/avalanchego/vms/avm/txs" "github.com/ava-labs/avalanchego/vms/avm/txs/executor" + "github.com/ava-labs/avalanchego/vms/avm/txs/fee" "github.com/ava-labs/avalanchego/vms/avm/txs/mempool" ) @@ -940,11 +941,13 @@ func defaultTestBackend(bootstrapped bool, sharedMemory atomic.SharedMemory) *ex Log: logging.NoLog{}, }, Config: &config.Config{ + StaticConfig: fee.StaticConfig{ + TxFee: 0, + CreateAssetTxFee: 0, + }, Upgrades: upgrade.Config{ EtnaTime: mockable.MaxTime, }, - TxFee: 0, - CreateAssetTxFee: 0, }, } } diff --git a/vms/avm/config/config.go b/vms/avm/config/config.go index 9fddc7427c6..ea929d64d33 100644 --- a/vms/avm/config/config.go +++ b/vms/avm/config/config.go @@ -3,15 +3,14 @@ package config -import "github.com/ava-labs/avalanchego/upgrade" +import ( + "github.com/ava-labs/avalanchego/upgrade" + "github.com/ava-labs/avalanchego/vms/avm/txs/fee" +) // Struct collecting all the foundational parameters of the AVM type Config struct { Upgrades upgrade.Config - // Fee that is burned by every non-asset creating transaction - TxFee uint64 - - // Fee that must be burned by every asset creating transaction - CreateAssetTxFee uint64 + fee.StaticConfig } diff --git a/vms/avm/environment_test.go b/vms/avm/environment_test.go index 75cae23f347..775460e4295 100644 --- a/vms/avm/environment_test.go +++ b/vms/avm/environment_test.go @@ -33,6 +33,7 @@ import ( "github.com/ava-labs/avalanchego/vms/avm/config" "github.com/ava-labs/avalanchego/vms/avm/fxs" "github.com/ava-labs/avalanchego/vms/avm/txs" + "github.com/ava-labs/avalanchego/vms/avm/txs/fee" "github.com/ava-labs/avalanchego/vms/avm/txs/txstest" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/nftfx" @@ -232,11 +233,13 @@ func setup(tb testing.TB, c *envConfig) *environment { func staticConfig(tb testing.TB, f fork) config.Config { c := config.Config{ + StaticConfig: fee.StaticConfig{ + TxFee: testTxFee, + CreateAssetTxFee: testTxFee, + }, Upgrades: upgrade.Config{ EtnaTime: mockable.MaxTime, }, - TxFee: testTxFee, - CreateAssetTxFee: testTxFee, } switch f { diff --git a/vms/avm/txs/executor/syntactic_verifier.go b/vms/avm/txs/executor/syntactic_verifier.go index 81a2f2a715f..b964ca1df02 100644 --- a/vms/avm/txs/executor/syntactic_verifier.go +++ b/vms/avm/txs/executor/syntactic_verifier.go @@ -13,6 +13,7 @@ import ( "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/avm/txs" + "github.com/ava-labs/avalanchego/vms/avm/txs/fee" "github.com/ava-labs/avalanchego/vms/components/avax" ) @@ -51,18 +52,7 @@ type SyntacticVerifier struct { } func (v *SyntacticVerifier) BaseTx(tx *txs.BaseTx) error { - if err := tx.BaseTx.Verify(v.Ctx); err != nil { - return err - } - - err := avax.VerifyTx( - v.Config.TxFee, - v.FeeAssetID, - [][]*avax.TransferableInput{tx.Ins}, - [][]*avax.TransferableOutput{tx.Outs}, - v.Codec, - ) - if err != nil { + if err := v.verifyBaseTx(tx, nil, nil); err != nil { return err } @@ -114,18 +104,7 @@ func (v *SyntacticVerifier) CreateAssetTx(tx *txs.CreateAssetTx) error { } } - if err := tx.BaseTx.BaseTx.Verify(v.Ctx); err != nil { - return err - } - - err := avax.VerifyTx( - v.Config.CreateAssetTxFee, - v.FeeAssetID, - [][]*avax.TransferableInput{tx.Ins}, - [][]*avax.TransferableOutput{tx.Outs}, - v.Codec, - ) - if err != nil { + if err := v.verifyBaseTx(&tx.BaseTx, nil, nil); err != nil { return err } @@ -162,18 +141,7 @@ func (v *SyntacticVerifier) OperationTx(tx *txs.OperationTx) error { return errNoOperations } - if err := tx.BaseTx.BaseTx.Verify(v.Ctx); err != nil { - return err - } - - err := avax.VerifyTx( - v.Config.TxFee, - v.FeeAssetID, - [][]*avax.TransferableInput{tx.Ins}, - [][]*avax.TransferableOutput{tx.Outs}, - v.Codec, - ) - if err != nil { + if err := v.verifyBaseTx(&tx.BaseTx, nil, nil); err != nil { return err } @@ -222,21 +190,7 @@ func (v *SyntacticVerifier) ImportTx(tx *txs.ImportTx) error { return errNoImportInputs } - if err := tx.BaseTx.BaseTx.Verify(v.Ctx); err != nil { - return err - } - - err := avax.VerifyTx( - v.Config.TxFee, - v.FeeAssetID, - [][]*avax.TransferableInput{ - tx.Ins, - tx.ImportedIns, - }, - [][]*avax.TransferableOutput{tx.Outs}, - v.Codec, - ) - if err != nil { + if err := v.verifyBaseTx(&tx.BaseTx, tx.ImportedIns, nil); err != nil { return err } @@ -264,21 +218,7 @@ func (v *SyntacticVerifier) ExportTx(tx *txs.ExportTx) error { return errNoExportOutputs } - if err := tx.BaseTx.BaseTx.Verify(v.Ctx); err != nil { - return err - } - - err := avax.VerifyTx( - v.Config.TxFee, - v.FeeAssetID, - [][]*avax.TransferableInput{tx.Ins}, - [][]*avax.TransferableOutput{ - tx.Outs, - tx.ExportedOuts, - }, - v.Codec, - ) - if err != nil { + if err := v.verifyBaseTx(&tx.BaseTx, nil, tx.ExportedOuts); err != nil { return err } @@ -300,3 +240,27 @@ func (v *SyntacticVerifier) ExportTx(tx *txs.ExportTx) error { return nil } + +func (v *SyntacticVerifier) verifyBaseTx( + bTx *txs.BaseTx, + importedIns []*avax.TransferableInput, + exportedOuts []*avax.TransferableOutput, +) error { + if err := bTx.BaseTx.Verify(v.Ctx); err != nil { + return err + } + + feeCalculator := fee.NewStaticCalculator(v.Backend.Config.StaticConfig) + fee, err := feeCalculator.CalculateFee(v.Tx) + if err != nil { + return err + } + + return avax.VerifyTx( + fee, + v.FeeAssetID, + [][]*avax.TransferableInput{bTx.Ins, importedIns}, + [][]*avax.TransferableOutput{bTx.Outs, exportedOuts}, + v.Codec, + ) +} diff --git a/vms/avm/txs/executor/syntactic_verifier_test.go b/vms/avm/txs/executor/syntactic_verifier_test.go index a5811163bad..8ef4bb09e17 100644 --- a/vms/avm/txs/executor/syntactic_verifier_test.go +++ b/vms/avm/txs/executor/syntactic_verifier_test.go @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/avalanchego/vms/avm/config" "github.com/ava-labs/avalanchego/vms/avm/fxs" "github.com/ava-labs/avalanchego/vms/avm/txs" + "github.com/ava-labs/avalanchego/vms/avm/txs/fee" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -29,11 +30,13 @@ import ( var ( keys = secp256k1.TestKeys() feeConfig = config.Config{ + StaticConfig: fee.StaticConfig{ + TxFee: 2, + CreateAssetTxFee: 3, + }, Upgrades: upgrade.Config{ EtnaTime: mockable.MaxTime, }, - TxFee: 2, - CreateAssetTxFee: 3, } ) diff --git a/vms/avm/txs/fee/calculator.go b/vms/avm/txs/fee/calculator.go new file mode 100644 index 00000000000..5acc94b05f6 --- /dev/null +++ b/vms/avm/txs/fee/calculator.go @@ -0,0 +1,11 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package fee + +import "github.com/ava-labs/avalanchego/vms/avm/txs" + +// Calculator is the interfaces that any fee Calculator must implement +type Calculator interface { + CalculateFee(tx *txs.Tx) (uint64, error) +} diff --git a/vms/avm/txs/fee/calculator_test.go b/vms/avm/txs/fee/calculator_test.go new file mode 100644 index 00000000000..59e4a291c19 --- /dev/null +++ b/vms/avm/txs/fee/calculator_test.go @@ -0,0 +1,93 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package fee + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/avm/txs" +) + +func TestTxFees(t *testing.T) { + feeTestsDefaultCfg := StaticConfig{ + TxFee: 1 * units.Avax, + CreateAssetTxFee: 2 * units.Avax, + } + + // chain times needed to have specific upgrades active + eUpgradeTime := time.Unix(1713945427, 0) + preEUpgradeTime := eUpgradeTime.Add(-1 * time.Second) + + tests := []struct { + name string + chainTime time.Time + unsignedTx func() txs.UnsignedTx + expected uint64 + }{ + { + name: "BaseTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: baseTx, + expected: feeTestsDefaultCfg.TxFee, + }, + { + name: "CreateAssetTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: createAssetTx, + expected: feeTestsDefaultCfg.CreateAssetTxFee, + }, + { + name: "OperationTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: operationTx, + expected: feeTestsDefaultCfg.TxFee, + }, + { + name: "ImportTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: importTx, + expected: feeTestsDefaultCfg.TxFee, + }, + { + name: "ExportTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: exportTx, + expected: feeTestsDefaultCfg.TxFee, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + uTx := tt.unsignedTx() + fc := NewStaticCalculator(feeTestsDefaultCfg) + haveFee, err := fc.CalculateFee(&txs.Tx{Unsigned: uTx}) + require.NoError(t, err) + require.Equal(t, tt.expected, haveFee) + }) + } +} + +func baseTx() txs.UnsignedTx { + return &txs.BaseTx{} +} + +func createAssetTx() txs.UnsignedTx { + return &txs.CreateAssetTx{} +} + +func operationTx() txs.UnsignedTx { + return &txs.OperationTx{} +} + +func importTx() txs.UnsignedTx { + return &txs.ImportTx{} +} + +func exportTx() txs.UnsignedTx { + return &txs.ExportTx{} +} diff --git a/vms/avm/txs/fee/static_calculator.go b/vms/avm/txs/fee/static_calculator.go new file mode 100644 index 00000000000..fe46a0caaf8 --- /dev/null +++ b/vms/avm/txs/fee/static_calculator.go @@ -0,0 +1,54 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package fee + +import "github.com/ava-labs/avalanchego/vms/avm/txs" + +var ( + _ Calculator = (*staticCalculator)(nil) + _ txs.Visitor = (*staticCalculator)(nil) +) + +func NewStaticCalculator(c StaticConfig) Calculator { + return &staticCalculator{staticCfg: c} +} + +func (c *staticCalculator) CalculateFee(tx *txs.Tx) (uint64, error) { + c.fee = 0 // zero fee among different calculateFee invocations (unlike gas which gets cumulated) + err := tx.Unsigned.Visit(c) + return c.fee, err +} + +type staticCalculator struct { + // inputs + staticCfg StaticConfig + + // outputs of visitor execution + fee uint64 +} + +func (c *staticCalculator) BaseTx(*txs.BaseTx) error { + c.fee = c.staticCfg.TxFee + return nil +} + +func (c *staticCalculator) CreateAssetTx(*txs.CreateAssetTx) error { + c.fee = c.staticCfg.CreateAssetTxFee + return nil +} + +func (c *staticCalculator) OperationTx(*txs.OperationTx) error { + c.fee = c.staticCfg.TxFee + return nil +} + +func (c *staticCalculator) ImportTx(*txs.ImportTx) error { + c.fee = c.staticCfg.TxFee + return nil +} + +func (c *staticCalculator) ExportTx(*txs.ExportTx) error { + c.fee = c.staticCfg.TxFee + return nil +} diff --git a/vms/avm/txs/fee/static_config.go b/vms/avm/txs/fee/static_config.go new file mode 100644 index 00000000000..a25fea8e95b --- /dev/null +++ b/vms/avm/txs/fee/static_config.go @@ -0,0 +1,12 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package fee + +type StaticConfig struct { + // Fee that is burned by every non-asset creating transaction + TxFee uint64 + + // Fee that must be burned by every asset creating transaction + CreateAssetTxFee uint64 +}