From 066c3a6ec71bf741ac43ebdf54c876b27d4a6102 Mon Sep 17 00:00:00 2001 From: Alberto Benegiamo Date: Tue, 21 May 2024 19:17:55 +0200 Subject: [PATCH] P-chain - introducing fees calculators (#2698) Signed-off-by: Alberto Benegiamo Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Co-authored-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- config/config.go | 7 +- genesis/genesis_fuji.go | 3 +- genesis/genesis_local.go | 3 +- genesis/genesis_mainnet.go | 3 +- genesis/params.go | 34 +-- node/config.go | 15 +- node/node.go | 49 ++-- vms/platformvm/block/builder/helpers_test.go | 19 +- vms/platformvm/block/executor/helpers_test.go | 19 +- .../block/executor/standard_block_test.go | 4 +- vms/platformvm/config/config.go | 45 +--- vms/platformvm/service_test.go | 10 +- .../txs/executor/create_chain_test.go | 2 +- .../txs/executor/create_subnet_test.go | 2 +- vms/platformvm/txs/executor/helpers_test.go | 19 +- vms/platformvm/txs/executor/import_test.go | 10 +- .../txs/executor/staker_tx_verification.go | 47 ++-- .../executor/staker_tx_verification_test.go | 4 +- .../txs/executor/standard_tx_executor.go | 36 ++- .../txs/executor/standard_tx_executor_test.go | 2 +- vms/platformvm/txs/fee/calculator.go | 142 ++++++++++ vms/platformvm/txs/fee/calculator_test.go | 251 ++++++++++++++++++ vms/platformvm/txs/fee/static_config.go | 33 +++ vms/platformvm/txs/txstest/context.go | 24 +- vms/platformvm/validator_set_property_test.go | 23 +- vms/platformvm/vm_regression_test.go | 8 +- vms/platformvm/vm_test.go | 27 +- 27 files changed, 640 insertions(+), 201 deletions(-) create mode 100644 vms/platformvm/txs/fee/calculator.go create mode 100644 vms/platformvm/txs/fee/calculator_test.go create mode 100644 vms/platformvm/txs/fee/static_config.go diff --git a/config/config.go b/config/config.go index 760c0022b123..4cc327240637 100644 --- a/config/config.go +++ b/config/config.go @@ -45,6 +45,7 @@ import ( "github.com/ava-labs/avalanchego/utils/timer" "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/platformvm/reward" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/proposervm" ) @@ -758,9 +759,9 @@ func getStakingConfig(v *viper.Viper, networkID uint32) (node.StakingConfig, err return config, nil } -func getTxFeeConfig(v *viper.Viper, networkID uint32) genesis.TxFeeConfig { +func getTxFeeConfig(v *viper.Viper, networkID uint32) fee.StaticConfig { if networkID != constants.MainnetID && networkID != constants.FujiID { - return genesis.TxFeeConfig{ + return fee.StaticConfig{ TxFee: v.GetUint64(TxFeeKey), CreateAssetTxFee: v.GetUint64(CreateAssetTxFeeKey), CreateSubnetTxFee: v.GetUint64(CreateSubnetTxFeeKey), @@ -1325,7 +1326,7 @@ func GetNodeConfig(v *viper.Viper) (node.Config, error) { nodeConfig.FdLimit = v.GetUint64(FdLimitKey) // Tx Fee - nodeConfig.TxFeeConfig = getTxFeeConfig(v, nodeConfig.NetworkID) + nodeConfig.StaticConfig = getTxFeeConfig(v, nodeConfig.NetworkID) // Genesis Data genesisStakingCfg := nodeConfig.StakingConfig.StakingConfig diff --git a/genesis/genesis_fuji.go b/genesis/genesis_fuji.go index 27c43f79fd0b..06cd2dd143ac 100644 --- a/genesis/genesis_fuji.go +++ b/genesis/genesis_fuji.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm/reward" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" ) var ( @@ -18,7 +19,7 @@ var ( // FujiParams are the params used for the fuji testnet FujiParams = Params{ - TxFeeConfig: TxFeeConfig{ + StaticConfig: fee.StaticConfig{ TxFee: units.MilliAvax, CreateAssetTxFee: 10 * units.MilliAvax, CreateSubnetTxFee: 100 * units.MilliAvax, diff --git a/genesis/genesis_local.go b/genesis/genesis_local.go index 5a76aa25cfcf..72f180ce3445 100644 --- a/genesis/genesis_local.go +++ b/genesis/genesis_local.go @@ -13,6 +13,7 @@ import ( "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/platformvm/reward" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" ) // PrivateKey-vmRQiZeXEXYMyJhEiqdC2z5JhuDbxL8ix9UVvjgMu2Er1NepE => P-local1g65uqn6t77p656w64023nh8nd9updzmxyymev2 @@ -36,7 +37,7 @@ var ( // LocalParams are the params used for local networks LocalParams = Params{ - TxFeeConfig: TxFeeConfig{ + StaticConfig: fee.StaticConfig{ TxFee: units.MilliAvax, CreateAssetTxFee: units.MilliAvax, CreateSubnetTxFee: 100 * units.MilliAvax, diff --git a/genesis/genesis_mainnet.go b/genesis/genesis_mainnet.go index 3808174ebbd1..94eae11cb2c8 100644 --- a/genesis/genesis_mainnet.go +++ b/genesis/genesis_mainnet.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm/reward" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" ) var ( @@ -18,7 +19,7 @@ var ( // MainnetParams are the params used for mainnet MainnetParams = Params{ - TxFeeConfig: TxFeeConfig{ + StaticConfig: fee.StaticConfig{ TxFee: units.MilliAvax, CreateAssetTxFee: 10 * units.MilliAvax, CreateSubnetTxFee: 1 * units.Avax, diff --git a/genesis/params.go b/genesis/params.go index e2ae45c697e6..6d4f5f4d978f 100644 --- a/genesis/params.go +++ b/genesis/params.go @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/vms/platformvm/reward" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" ) type StakingConfig struct { @@ -33,42 +34,21 @@ type StakingConfig struct { RewardConfig reward.Config `json:"rewardConfig"` } -type TxFeeConfig struct { - // Transaction fee - TxFee uint64 `json:"txFee"` - // Transaction fee for create asset transactions - CreateAssetTxFee uint64 `json:"createAssetTxFee"` - // Transaction fee for create subnet transactions - CreateSubnetTxFee uint64 `json:"createSubnetTxFee"` - // Transaction fee for transform subnet transactions - TransformSubnetTxFee uint64 `json:"transformSubnetTxFee"` - // Transaction fee for create blockchain transactions - CreateBlockchainTxFee uint64 `json:"createBlockchainTxFee"` - // Transaction fee for adding a primary network validator - AddPrimaryNetworkValidatorFee uint64 `json:"addPrimaryNetworkValidatorFee"` - // Transaction fee for adding a primary network delegator - AddPrimaryNetworkDelegatorFee uint64 `json:"addPrimaryNetworkDelegatorFee"` - // Transaction fee for adding a subnet validator - AddSubnetValidatorFee uint64 `json:"addSubnetValidatorFee"` - // Transaction fee for adding a subnet delegator - AddSubnetDelegatorFee uint64 `json:"addSubnetDelegatorFee"` -} - type Params struct { StakingConfig - TxFeeConfig + fee.StaticConfig } -func GetTxFeeConfig(networkID uint32) TxFeeConfig { +func GetTxFeeConfig(networkID uint32) fee.StaticConfig { switch networkID { case constants.MainnetID: - return MainnetParams.TxFeeConfig + return MainnetParams.StaticConfig case constants.FujiID: - return FujiParams.TxFeeConfig + return FujiParams.StaticConfig case constants.LocalID: - return LocalParams.TxFeeConfig + return LocalParams.StaticConfig default: - return LocalParams.TxFeeConfig + return LocalParams.StaticConfig } } diff --git a/node/config.go b/node/config.go index 716b9a32c90a..f5f8c1332530 100644 --- a/node/config.go +++ b/node/config.go @@ -23,6 +23,7 @@ import ( "github.com/ava-labs/avalanchego/utils/profiler" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/timer" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" ) type APIIndexerConfig struct { @@ -122,13 +123,13 @@ type DatabaseConfig struct { // Config contains all of the configurations of an Avalanche node. type Config struct { - HTTPConfig `json:"httpConfig"` - IPConfig `json:"ipConfig"` - StakingConfig `json:"stakingConfig"` - genesis.TxFeeConfig `json:"txFeeConfig"` - StateSyncConfig `json:"stateSyncConfig"` - BootstrapConfig `json:"bootstrapConfig"` - DatabaseConfig `json:"databaseConfig"` + HTTPConfig `json:"httpConfig"` + IPConfig `json:"ipConfig"` + StakingConfig `json:"stakingConfig"` + fee.StaticConfig `json:"txFeeConfig"` + StateSyncConfig `json:"stateSyncConfig"` + BootstrapConfig `json:"bootstrapConfig"` + DatabaseConfig `json:"databaseConfig"` // Genesis information GenesisBytes []byte `json:"-"` diff --git a/node/node.go b/node/node.go index 9144541f8a3a..fff8e13572cb 100644 --- a/node/node.go +++ b/node/node.go @@ -75,6 +75,7 @@ import ( "github.com/ava-labs/avalanchego/vms/avm" "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/signer" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm/upgrade" "github.com/ava-labs/avalanchego/vms/registry" "github.com/ava-labs/avalanchego/vms/rpcchainvm/runtime" @@ -1127,29 +1128,31 @@ func (n *Node) initVMs() error { err := utils.Err( n.VMManager.RegisterFactory(context.TODO(), constants.PlatformVMID, &platformvm.Factory{ Config: platformconfig.Config{ - Chains: n.chainManager, - Validators: vdrs, - UptimeLockedCalculator: n.uptimeCalculator, - SybilProtectionEnabled: n.Config.SybilProtectionEnabled, - PartialSyncPrimaryNetwork: n.Config.PartialSyncPrimaryNetwork, - TrackedSubnets: n.Config.TrackedSubnets, - TxFee: n.Config.TxFee, - CreateAssetTxFee: n.Config.CreateAssetTxFee, - CreateSubnetTxFee: n.Config.CreateSubnetTxFee, - TransformSubnetTxFee: n.Config.TransformSubnetTxFee, - CreateBlockchainTxFee: n.Config.CreateBlockchainTxFee, - AddPrimaryNetworkValidatorFee: n.Config.AddPrimaryNetworkValidatorFee, - AddPrimaryNetworkDelegatorFee: n.Config.AddPrimaryNetworkDelegatorFee, - AddSubnetValidatorFee: n.Config.AddSubnetValidatorFee, - AddSubnetDelegatorFee: n.Config.AddSubnetDelegatorFee, - UptimePercentage: n.Config.UptimeRequirement, - MinValidatorStake: n.Config.MinValidatorStake, - MaxValidatorStake: n.Config.MaxValidatorStake, - MinDelegatorStake: n.Config.MinDelegatorStake, - MinDelegationFee: n.Config.MinDelegationFee, - MinStakeDuration: n.Config.MinStakeDuration, - MaxStakeDuration: n.Config.MaxStakeDuration, - RewardConfig: n.Config.RewardConfig, + Chains: n.chainManager, + Validators: vdrs, + UptimeLockedCalculator: n.uptimeCalculator, + SybilProtectionEnabled: n.Config.SybilProtectionEnabled, + PartialSyncPrimaryNetwork: n.Config.PartialSyncPrimaryNetwork, + TrackedSubnets: n.Config.TrackedSubnets, + StaticFeeConfig: fee.StaticConfig{ + TxFee: n.Config.TxFee, + CreateAssetTxFee: n.Config.CreateAssetTxFee, + CreateSubnetTxFee: n.Config.CreateSubnetTxFee, + TransformSubnetTxFee: n.Config.TransformSubnetTxFee, + CreateBlockchainTxFee: n.Config.CreateBlockchainTxFee, + AddPrimaryNetworkValidatorFee: n.Config.AddPrimaryNetworkValidatorFee, + AddPrimaryNetworkDelegatorFee: n.Config.AddPrimaryNetworkDelegatorFee, + AddSubnetValidatorFee: n.Config.AddSubnetValidatorFee, + AddSubnetDelegatorFee: n.Config.AddSubnetDelegatorFee, + }, + UptimePercentage: n.Config.UptimeRequirement, + MinValidatorStake: n.Config.MinValidatorStake, + MaxValidatorStake: n.Config.MaxValidatorStake, + MinDelegatorStake: n.Config.MinDelegatorStake, + MinDelegationFee: n.Config.MinDelegationFee, + MinStakeDuration: n.Config.MinStakeDuration, + MaxStakeDuration: n.Config.MaxStakeDuration, + RewardConfig: n.Config.RewardConfig, UpgradeConfig: upgrade.Config{ ApricotPhase3Time: version.GetApricotPhase3Time(n.Config.NetworkID), ApricotPhase5Time: version.GetApricotPhase5Time(n.Config.NetworkID), diff --git a/vms/platformvm/block/builder/helpers_test.go b/vms/platformvm/block/builder/helpers_test.go index e9310ac05b4f..0108162649d6 100644 --- a/vms/platformvm/block/builder/helpers_test.go +++ b/vms/platformvm/block/builder/helpers_test.go @@ -43,6 +43,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/status" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest" "github.com/ava-labs/avalanchego/vms/platformvm/upgrade" @@ -308,14 +309,16 @@ func defaultConfig(t *testing.T, f fork) *config.Config { Chains: chains.TestManager, UptimeLockedCalculator: uptime.NewLockedCalculator(), Validators: validators.NewManager(), - TxFee: defaultTxFee, - CreateSubnetTxFee: 100 * defaultTxFee, - CreateBlockchainTxFee: 100 * defaultTxFee, - MinValidatorStake: 5 * units.MilliAvax, - MaxValidatorStake: 500 * units.MilliAvax, - MinDelegatorStake: 1 * units.MilliAvax, - MinStakeDuration: defaultMinStakingDuration, - MaxStakeDuration: defaultMaxStakingDuration, + StaticFeeConfig: fee.StaticConfig{ + TxFee: defaultTxFee, + CreateSubnetTxFee: 100 * defaultTxFee, + CreateBlockchainTxFee: 100 * defaultTxFee, + }, + MinValidatorStake: 5 * units.MilliAvax, + MaxValidatorStake: 500 * units.MilliAvax, + MinDelegatorStake: 1 * units.MilliAvax, + MinStakeDuration: defaultMinStakingDuration, + MaxStakeDuration: defaultMaxStakingDuration, RewardConfig: reward.Config{ MaxConsumptionRate: .12 * reward.PercentDenominator, MinConsumptionRate: .10 * reward.PercentDenominator, diff --git a/vms/platformvm/block/executor/helpers_test.go b/vms/platformvm/block/executor/helpers_test.go index c1295f862a6c..d87b67c76d25 100644 --- a/vms/platformvm/block/executor/helpers_test.go +++ b/vms/platformvm/block/executor/helpers_test.go @@ -44,6 +44,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/status" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest" "github.com/ava-labs/avalanchego/vms/platformvm/upgrade" @@ -330,14 +331,16 @@ func defaultConfig(t *testing.T, f fork) *config.Config { Chains: chains.TestManager, UptimeLockedCalculator: uptime.NewLockedCalculator(), Validators: validators.NewManager(), - TxFee: defaultTxFee, - CreateSubnetTxFee: 100 * defaultTxFee, - CreateBlockchainTxFee: 100 * defaultTxFee, - MinValidatorStake: 5 * units.MilliAvax, - MaxValidatorStake: 500 * units.MilliAvax, - MinDelegatorStake: 1 * units.MilliAvax, - MinStakeDuration: defaultMinStakingDuration, - MaxStakeDuration: defaultMaxStakingDuration, + StaticFeeConfig: fee.StaticConfig{ + TxFee: defaultTxFee, + CreateSubnetTxFee: 100 * defaultTxFee, + CreateBlockchainTxFee: 100 * defaultTxFee, + }, + MinValidatorStake: 5 * units.MilliAvax, + MaxValidatorStake: 500 * units.MilliAvax, + MinDelegatorStake: 1 * units.MilliAvax, + MinStakeDuration: defaultMinStakingDuration, + MaxStakeDuration: defaultMaxStakingDuration, RewardConfig: reward.Config{ MaxConsumptionRate: .12 * reward.PercentDenominator, MinConsumptionRate: .10 * reward.PercentDenominator, diff --git a/vms/platformvm/block/executor/standard_block_test.go b/vms/platformvm/block/executor/standard_block_test.go index 880b706884e1..b8c9257a2915 100644 --- a/vms/platformvm/block/executor/standard_block_test.go +++ b/vms/platformvm/block/executor/standard_block_test.go @@ -143,7 +143,7 @@ func TestBanffStandardBlockTimeVerification(t *testing.T) { ID: avaxAssetID, }, Out: &secp256k1fx.TransferOutput{ - Amt: env.config.CreateSubnetTxFee, + Amt: env.config.StaticFeeConfig.CreateSubnetTxFee, }, } utxoID := utxo.InputID() @@ -158,7 +158,7 @@ func TestBanffStandardBlockTimeVerification(t *testing.T) { UTXOID: utxo.UTXOID, Asset: utxo.Asset, In: &secp256k1fx.TransferInput{ - Amt: env.config.CreateSubnetTxFee, + Amt: env.config.StaticFeeConfig.CreateSubnetTxFee, }, }}, }}, diff --git a/vms/platformvm/config/config.go b/vms/platformvm/config/config.go index 8b8a0c717aed..731b079ca425 100644 --- a/vms/platformvm/config/config.go +++ b/vms/platformvm/config/config.go @@ -14,6 +14,7 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/reward" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm/upgrade" ) @@ -30,6 +31,9 @@ type Config struct { // calling VM.Initialize. Validators validators.Manager + // All static fees config active before E-upgrade + StaticFeeConfig fee.StaticConfig + // Provides access to the uptime manager as a thread safe data structure UptimeLockedCalculator uptime.LockedCalculator @@ -42,33 +46,6 @@ type Config struct { // Set of subnets that this node is validating TrackedSubnets set.Set[ids.ID] - // Fee that is burned by every non-state creating transaction - TxFee uint64 - - // Fee that must be burned by every state creating transaction before AP3 - CreateAssetTxFee uint64 - - // Fee that must be burned by every subnet creating transaction after AP3 - CreateSubnetTxFee uint64 - - // Fee that must be burned by every transform subnet transaction - TransformSubnetTxFee uint64 - - // Fee that must be burned by every blockchain creating transaction after AP3 - CreateBlockchainTxFee uint64 - - // Transaction fee for adding a primary network validator - AddPrimaryNetworkValidatorFee uint64 - - // Transaction fee for adding a primary network delegator - AddPrimaryNetworkDelegatorFee uint64 - - // Transaction fee for adding a subnet validator - AddSubnetValidatorFee uint64 - - // Transaction fee for adding a subnet delegator - AddSubnetDelegatorFee uint64 - // The minimum amount of tokens one must bond to be a validator MinValidatorStake uint64 @@ -106,20 +83,6 @@ type Config struct { UseCurrentHeight bool } -func (c *Config) GetCreateBlockchainTxFee(timestamp time.Time) uint64 { - if c.UpgradeConfig.IsApricotPhase3Activated(timestamp) { - return c.CreateBlockchainTxFee - } - return c.CreateAssetTxFee -} - -func (c *Config) GetCreateSubnetTxFee(timestamp time.Time) uint64 { - if c.UpgradeConfig.IsApricotPhase3Activated(timestamp) { - return c.CreateSubnetTxFee - } - return c.CreateAssetTxFee -} - // Create the blockchain described in [tx], but only if this node is a member of // the subnet that validates the chain func (c *Config) CreateChain(chainID ids.ID, tx *txs.CreateChainTx) { diff --git a/vms/platformvm/service_test.go b/vms/platformvm/service_test.go index e44e603ce53d..95e2f98c3228 100644 --- a/vms/platformvm/service_test.go +++ b/vms/platformvm/service_test.go @@ -39,6 +39,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/status" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" @@ -366,6 +367,11 @@ func TestGetBalance(t *testing.T) { require := require.New(t) service, _, _ := defaultService(t) + var ( + feeCalc = fee.NewStaticCalculator(service.vm.Config.StaticFeeConfig, service.vm.Config.UpgradeConfig) + createSubnetFee = feeCalc.CalculateFee(&txs.CreateSubnetTx{}, service.vm.clock.Time()) + ) + // Ensure GetStake is correct for each of the genesis validators genesis, _ := defaultGenesis(t, service.vm.ctx.AVAXAssetID) for idx, utxo := range genesis.UTXOs { @@ -381,7 +387,7 @@ func TestGetBalance(t *testing.T) { if idx == 0 { // we use the first key to fund a subnet creation in [defaultGenesis]. // As such we need to account for the subnet creation fee - balance = defaultBalance - service.vm.Config.GetCreateSubnetTxFee(service.vm.clock.Time()) + balance = defaultBalance - createSubnetFee } require.Equal(avajson.Uint64(balance), reply.Balance) require.Equal(avajson.Uint64(balance), reply.Unlocked) @@ -750,7 +756,7 @@ func TestGetBlock(t *testing.T) { service, _, txBuilder := defaultService(t) service.vm.ctx.Lock.Lock() - service.vm.Config.CreateAssetTxFee = 100 * defaultTxFee + service.vm.StaticFeeConfig.CreateAssetTxFee = 100 * defaultTxFee // Make a block an accept it, then check we can get it. tx, err := txBuilder.NewCreateChainTx( // Test GetTx works for standard blocks diff --git a/vms/platformvm/txs/executor/create_chain_test.go b/vms/platformvm/txs/executor/create_chain_test.go index 288a294e493b..346c8ab1468a 100644 --- a/vms/platformvm/txs/executor/create_chain_test.go +++ b/vms/platformvm/txs/executor/create_chain_test.go @@ -195,7 +195,7 @@ func TestCreateChainTxAP3FeeChange(t *testing.T) { env.state.SetTimestamp(test.time) // to duly set fee cfg := *env.config - cfg.CreateBlockchainTxFee = test.fee + cfg.StaticFeeConfig.CreateBlockchainTxFee = test.fee builder := txstest.NewBuilder(env.ctx, &cfg, env.state) tx, err := builder.NewCreateChainTx( testSubnet1.ID(), diff --git a/vms/platformvm/txs/executor/create_subnet_test.go b/vms/platformvm/txs/executor/create_subnet_test.go index 77b37773107b..c1902dd56625 100644 --- a/vms/platformvm/txs/executor/create_subnet_test.go +++ b/vms/platformvm/txs/executor/create_subnet_test.go @@ -62,7 +62,7 @@ func TestCreateSubnetTxAP3FeeChange(t *testing.T) { } cfg := *env.config - cfg.CreateSubnetTxFee = test.fee + cfg.StaticFeeConfig.CreateSubnetTxFee = test.fee builder := txstest.NewBuilder(env.ctx, &cfg, env.state) tx, err := builder.NewCreateSubnetTx( &secp256k1fx.OutputOwners{}, diff --git a/vms/platformvm/txs/executor/helpers_test.go b/vms/platformvm/txs/executor/helpers_test.go index 994250dd5940..74c706227fea 100644 --- a/vms/platformvm/txs/executor/helpers_test.go +++ b/vms/platformvm/txs/executor/helpers_test.go @@ -41,6 +41,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/status" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest" "github.com/ava-labs/avalanchego/vms/platformvm/upgrade" "github.com/ava-labs/avalanchego/vms/platformvm/utxo" @@ -279,14 +280,16 @@ func defaultConfig(t *testing.T, f fork) *config.Config { Chains: chains.TestManager, UptimeLockedCalculator: uptime.NewLockedCalculator(), Validators: validators.NewManager(), - TxFee: defaultTxFee, - CreateSubnetTxFee: 100 * defaultTxFee, - CreateBlockchainTxFee: 100 * defaultTxFee, - MinValidatorStake: 5 * units.MilliAvax, - MaxValidatorStake: 500 * units.MilliAvax, - MinDelegatorStake: 1 * units.MilliAvax, - MinStakeDuration: defaultMinStakingDuration, - MaxStakeDuration: defaultMaxStakingDuration, + StaticFeeConfig: fee.StaticConfig{ + TxFee: defaultTxFee, + CreateSubnetTxFee: 100 * defaultTxFee, + CreateBlockchainTxFee: 100 * defaultTxFee, + }, + MinValidatorStake: 5 * units.MilliAvax, + MaxValidatorStake: 500 * units.MilliAvax, + MinDelegatorStake: 1 * units.MilliAvax, + MinStakeDuration: defaultMinStakingDuration, + MaxStakeDuration: defaultMaxStakingDuration, RewardConfig: reward.Config{ MaxConsumptionRate: .12 * reward.PercentDenominator, MinConsumptionRate: .10 * reward.PercentDenominator, diff --git a/vms/platformvm/txs/executor/import_test.go b/vms/platformvm/txs/executor/import_test.go index c4b831badc92..7bb0be9afcc1 100644 --- a/vms/platformvm/txs/executor/import_test.go +++ b/vms/platformvm/txs/executor/import_test.go @@ -51,7 +51,7 @@ func TestNewImportTx(t *testing.T) { sourceKey, env.ctx.XChainID, map[ids.ID]uint64{ - env.ctx.AVAXAssetID: env.config.TxFee - 1, + env.ctx.AVAXAssetID: env.config.StaticFeeConfig.TxFee - 1, }, randSrc, ), @@ -67,7 +67,7 @@ func TestNewImportTx(t *testing.T) { sourceKey, env.ctx.XChainID, map[ids.ID]uint64{ - env.ctx.AVAXAssetID: env.config.TxFee, + env.ctx.AVAXAssetID: env.config.StaticFeeConfig.TxFee, }, randSrc, ), @@ -83,7 +83,7 @@ func TestNewImportTx(t *testing.T) { sourceKey, env.ctx.CChainID, map[ids.ID]uint64{ - env.ctx.AVAXAssetID: env.config.TxFee, + env.ctx.AVAXAssetID: env.config.StaticFeeConfig.TxFee, }, randSrc, ), @@ -100,7 +100,7 @@ func TestNewImportTx(t *testing.T) { sourceKey, env.ctx.XChainID, map[ids.ID]uint64{ - env.ctx.AVAXAssetID: env.config.TxFee, + env.ctx.AVAXAssetID: env.config.StaticFeeConfig.TxFee, customAssetID: 1, }, randSrc, @@ -148,7 +148,7 @@ func TestNewImportTx(t *testing.T) { totalOut += out.Out.Amount() } - require.Equal(env.config.TxFee, totalIn-totalOut) + require.Equal(env.config.StaticFeeConfig.TxFee, totalIn-totalOut) stateDiff, err := state.NewDiff(lastAcceptedID, env) require.NoError(err) diff --git a/vms/platformvm/txs/executor/staker_tx_verification.go b/vms/platformvm/txs/executor/staker_tx_verification.go index befd736a674b..0aac4ad50f64 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification.go +++ b/vms/platformvm/txs/executor/staker_tx_verification.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" safemath "github.com/ava-labs/avalanchego/utils/math" ) @@ -163,6 +164,9 @@ func verifyAddValidatorTx( } // Verify the flowcheck + feeCalculator := fee.NewStaticCalculator(backend.Config.StaticFeeConfig, backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := backend.FlowChecker.VerifySpend( tx, chainState, @@ -170,7 +174,7 @@ func verifyAddValidatorTx( outs, sTx.Creds, map[ids.ID]uint64{ - backend.Ctx.AVAXAssetID: backend.Config.AddPrimaryNetworkValidatorFee, + backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return nil, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) @@ -251,6 +255,9 @@ func verifyAddSubnetValidatorTx( } // Verify the flowcheck + feeCalculator := fee.NewStaticCalculator(backend.Config.StaticFeeConfig, backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := backend.FlowChecker.VerifySpend( tx, chainState, @@ -258,7 +265,7 @@ func verifyAddSubnetValidatorTx( tx.Outs, baseTxCreds, map[ids.ID]uint64{ - backend.Ctx.AVAXAssetID: backend.Config.AddSubnetValidatorFee, + backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) @@ -326,6 +333,9 @@ func verifyRemoveSubnetValidatorTx( } // Verify the flowcheck + feeCalculator := fee.NewStaticCalculator(backend.Config.StaticFeeConfig, backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := backend.FlowChecker.VerifySpend( tx, chainState, @@ -333,7 +343,7 @@ func verifyRemoveSubnetValidatorTx( tx.Outs, baseTxCreds, map[ids.ID]uint64{ - backend.Ctx.AVAXAssetID: backend.Config.TxFee, + backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return nil, false, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) @@ -441,6 +451,9 @@ func verifyAddDelegatorTx( } // Verify the flowcheck + feeCalculator := fee.NewStaticCalculator(backend.Config.StaticFeeConfig, backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := backend.FlowChecker.VerifySpend( tx, chainState, @@ -448,7 +461,7 @@ func verifyAddDelegatorTx( outs, sTx.Creds, map[ids.ID]uint64{ - backend.Ctx.AVAXAssetID: backend.Config.AddPrimaryNetworkDelegatorFee, + backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return nil, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) @@ -547,15 +560,10 @@ func verifyAddPermissionlessValidatorTx( ) } - var txFee uint64 if tx.Subnet != constants.PrimaryNetworkID { if err := verifySubnetValidatorPrimaryNetworkRequirements(isDurangoActive, chainState, tx.Validator); err != nil { return err } - - txFee = backend.Config.AddSubnetValidatorFee - } else { - txFee = backend.Config.AddPrimaryNetworkValidatorFee } outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts)) @@ -563,6 +571,9 @@ func verifyAddPermissionlessValidatorTx( copy(outs[len(tx.Outs):], tx.StakeOuts) // Verify the flowcheck + feeCalculator := fee.NewStaticCalculator(backend.Config.StaticFeeConfig, backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := backend.FlowChecker.VerifySpend( tx, chainState, @@ -570,7 +581,7 @@ func verifyAddPermissionlessValidatorTx( outs, sTx.Creds, map[ids.ID]uint64{ - backend.Ctx.AVAXAssetID: txFee, + backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) @@ -692,7 +703,6 @@ func verifyAddPermissionlessDelegatorTx( copy(outs, tx.Outs) copy(outs[len(tx.Outs):], tx.StakeOuts) - var txFee uint64 if tx.Subnet != constants.PrimaryNetworkID { // Invariant: Delegators must only be able to reference validator // transactions that implement [txs.ValidatorTx]. All @@ -703,13 +713,12 @@ func verifyAddPermissionlessDelegatorTx( if validator.Priority.IsPermissionedValidator() { return ErrDelegateToPermissionedValidator } - - txFee = backend.Config.AddSubnetDelegatorFee - } else { - txFee = backend.Config.AddPrimaryNetworkDelegatorFee } // Verify the flowcheck + feeCalculator := fee.NewStaticCalculator(backend.Config.StaticFeeConfig, backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := backend.FlowChecker.VerifySpend( tx, chainState, @@ -717,7 +726,7 @@ func verifyAddPermissionlessDelegatorTx( outs, sTx.Creds, map[ids.ID]uint64{ - backend.Ctx.AVAXAssetID: txFee, + backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) @@ -761,6 +770,10 @@ func verifyTransferSubnetOwnershipTx( } // Verify the flowcheck + currentTimestamp := chainState.GetTimestamp() + feeCalculator := fee.NewStaticCalculator(backend.Config.StaticFeeConfig, backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := backend.FlowChecker.VerifySpend( tx, chainState, @@ -768,7 +781,7 @@ func verifyTransferSubnetOwnershipTx( tx.Outs, baseTxCreds, map[ids.ID]uint64{ - backend.Ctx.AVAXAssetID: backend.Config.TxFee, + backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) diff --git a/vms/platformvm/txs/executor/staker_tx_verification_test.go b/vms/platformvm/txs/executor/staker_tx_verification_test.go index 24e1df2a0db6..bde3da64ad7a 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification_test.go +++ b/vms/platformvm/txs/executor/staker_tx_verification_test.go @@ -423,7 +423,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { ).Return(ErrFlowCheckFailed) cfg := defaultTestConfig(t, durango, activeForkTime) - cfg.AddSubnetValidatorFee = 1 + cfg.StaticFeeConfig.AddSubnetValidatorFee = 1 return &Backend{ FlowChecker: flowChecker, @@ -469,7 +469,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { ).Return(nil) cfg := defaultTestConfig(t, durango, activeForkTime) - cfg.AddSubnetValidatorFee = 1 + cfg.StaticFeeConfig.AddSubnetValidatorFee = 1 return &Backend{ FlowChecker: flowChecker, diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 4ee5ced73173..725f1aaff814 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" ) var ( @@ -68,7 +69,9 @@ func (e *StandardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { } // Verify the flowcheck - createBlockchainTxFee := e.Config.GetCreateBlockchainTxFee(currentTimestamp) + feeCalculator := fee.NewStaticCalculator(e.Backend.Config.StaticFeeConfig, e.Backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := e.FlowChecker.VerifySpend( tx, e.State, @@ -76,7 +79,7 @@ func (e *StandardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { tx.Outs, baseTxCreds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: createBlockchainTxFee, + e.Ctx.AVAXAssetID: fee, }, ); err != nil { return err @@ -114,7 +117,9 @@ func (e *StandardTxExecutor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { } // Verify the flowcheck - createSubnetTxFee := e.Config.GetCreateSubnetTxFee(currentTimestamp) + feeCalculator := fee.NewStaticCalculator(e.Backend.Config.StaticFeeConfig, e.Backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := e.FlowChecker.VerifySpend( tx, e.State, @@ -122,7 +127,7 @@ func (e *StandardTxExecutor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { tx.Outs, e.Tx.Creds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: createSubnetTxFee, + e.Ctx.AVAXAssetID: fee, }, ); err != nil { return err @@ -194,6 +199,10 @@ func (e *StandardTxExecutor) ImportTx(tx *txs.ImportTx) error { copy(ins, tx.Ins) copy(ins[len(tx.Ins):], tx.ImportedInputs) + // Verify the flowcheck + feeCalculator := fee.NewStaticCalculator(e.Backend.Config.StaticFeeConfig, e.Backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := e.FlowChecker.VerifySpendUTXOs( tx, utxos, @@ -201,7 +210,7 @@ func (e *StandardTxExecutor) ImportTx(tx *txs.ImportTx) error { tx.Outs, e.Tx.Creds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: e.Config.TxFee, + e.Ctx.AVAXAssetID: fee, }, ); err != nil { return err @@ -250,6 +259,9 @@ func (e *StandardTxExecutor) ExportTx(tx *txs.ExportTx) error { } // Verify the flowcheck + feeCalculator := fee.NewStaticCalculator(e.Backend.Config.StaticFeeConfig, e.Backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := e.FlowChecker.VerifySpend( tx, e.State, @@ -257,7 +269,7 @@ func (e *StandardTxExecutor) ExportTx(tx *txs.ExportTx) error { outs, e.Tx.Creds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: e.Config.TxFee, + e.Ctx.AVAXAssetID: fee, }, ); err != nil { return fmt.Errorf("failed verifySpend: %w", err) @@ -435,6 +447,10 @@ func (e *StandardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error return err } + // Verify the flowcheck + feeCalculator := fee.NewStaticCalculator(e.Backend.Config.StaticFeeConfig, e.Backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + totalRewardAmount := tx.MaximumSupply - tx.InitialSupply if err := e.Backend.FlowChecker.VerifySpend( tx, @@ -446,7 +462,7 @@ func (e *StandardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error // entry in this map literal from being overwritten by the // second entry. map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: e.Config.TransformSubnetTxFee, + e.Ctx.AVAXAssetID: fee, tx.AssetID: totalRewardAmount, }, ); err != nil { @@ -555,6 +571,10 @@ func (e *StandardTxExecutor) BaseTx(tx *txs.BaseTx) error { } // Verify the flowcheck + currentTimestamp := e.State.GetTimestamp() + feeCalculator := fee.NewStaticCalculator(e.Backend.Config.StaticFeeConfig, e.Backend.Config.UpgradeConfig) + fee := feeCalculator.CalculateFee(tx, currentTimestamp) + if err := e.FlowChecker.VerifySpend( tx, e.State, @@ -562,7 +582,7 @@ func (e *StandardTxExecutor) BaseTx(tx *txs.BaseTx) error { tx.Outs, e.Tx.Creds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: e.Config.TxFee, + e.Ctx.AVAXAssetID: fee, }, ); err != nil { return err diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 47f26ac5dd13..de69b0ff5a8c 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -1588,7 +1588,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) // Set dependency expectations. - env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() + env.state.EXPECT().GetTimestamp().Return(env.latestForkTime) env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil).Times(1) subnetOwner := fx.NewMockOwner(ctrl) env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1) diff --git a/vms/platformvm/txs/fee/calculator.go b/vms/platformvm/txs/fee/calculator.go new file mode 100644 index 000000000000..f349f282f7ca --- /dev/null +++ b/vms/platformvm/txs/fee/calculator.go @@ -0,0 +1,142 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package fee + +import ( + "time" + + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/upgrade" +) + +var _ txs.Visitor = (*calculator)(nil) + +func NewStaticCalculator(config StaticConfig, upgradeTimes upgrade.Config) *Calculator { + return &Calculator{ + config: config, + upgradeTimes: upgradeTimes, + } +} + +type Calculator struct { + config StaticConfig + upgradeTimes upgrade.Config +} + +// [CalculateFee] returns the minimal fee needed to accept [tx], at chain time [time] +func (c *Calculator) CalculateFee(tx txs.UnsignedTx, time time.Time) uint64 { + tmp := &calculator{ + upgrades: c.upgradeTimes, + staticCfg: c.config, + time: time, + } + + // this is guaranteed to never return an error + _ = tx.Visit(tmp) + return tmp.fee +} + +// calculator is intentionally unexported and used through Calculator to provide +// a more convenient API +type calculator struct { + // Pre E-fork inputs + upgrades upgrade.Config + staticCfg StaticConfig + time time.Time + + // outputs of visitor execution + fee uint64 +} + +func (c *calculator) AddValidatorTx(*txs.AddValidatorTx) error { + c.fee = c.staticCfg.AddPrimaryNetworkValidatorFee + return nil +} + +func (c *calculator) AddSubnetValidatorTx(*txs.AddSubnetValidatorTx) error { + c.fee = c.staticCfg.AddSubnetValidatorFee + return nil +} + +func (c *calculator) AddDelegatorTx(*txs.AddDelegatorTx) error { + c.fee = c.staticCfg.AddPrimaryNetworkDelegatorFee + return nil +} + +func (c *calculator) CreateChainTx(*txs.CreateChainTx) error { + if c.upgrades.IsApricotPhase3Activated(c.time) { + c.fee = c.staticCfg.CreateBlockchainTxFee + } else { + c.fee = c.staticCfg.CreateAssetTxFee + } + return nil +} + +func (c *calculator) CreateSubnetTx(*txs.CreateSubnetTx) error { + if c.upgrades.IsApricotPhase3Activated(c.time) { + c.fee = c.staticCfg.CreateSubnetTxFee + } else { + c.fee = c.staticCfg.CreateAssetTxFee + } + return nil +} + +func (c *calculator) AdvanceTimeTx(*txs.AdvanceTimeTx) error { + c.fee = 0 // no fees + return nil +} + +func (c *calculator) RewardValidatorTx(*txs.RewardValidatorTx) error { + c.fee = 0 // no fees + return nil +} + +func (c *calculator) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { + c.fee = c.staticCfg.TxFee + return nil +} + +func (c *calculator) TransformSubnetTx(*txs.TransformSubnetTx) error { + c.fee = c.staticCfg.TransformSubnetTxFee + return nil +} + +func (c *calculator) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { + c.fee = c.staticCfg.TxFee + return nil +} + +func (c *calculator) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { + if tx.Subnet != constants.PrimaryNetworkID { + c.fee = c.staticCfg.AddSubnetValidatorFee + } else { + c.fee = c.staticCfg.AddPrimaryNetworkValidatorFee + } + return nil +} + +func (c *calculator) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { + if tx.Subnet != constants.PrimaryNetworkID { + c.fee = c.staticCfg.AddSubnetDelegatorFee + } else { + c.fee = c.staticCfg.AddPrimaryNetworkDelegatorFee + } + return nil +} + +func (c *calculator) BaseTx(*txs.BaseTx) error { + c.fee = c.staticCfg.TxFee + return nil +} + +func (c *calculator) ImportTx(*txs.ImportTx) error { + c.fee = c.staticCfg.TxFee + return nil +} + +func (c *calculator) ExportTx(*txs.ExportTx) error { + c.fee = c.staticCfg.TxFee + return nil +} diff --git a/vms/platformvm/txs/fee/calculator_test.go b/vms/platformvm/txs/fee/calculator_test.go new file mode 100644 index 000000000000..c25fec9073e8 --- /dev/null +++ b/vms/platformvm/txs/fee/calculator_test.go @@ -0,0 +1,251 @@ +// 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/ids" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/upgrade" +) + +func TestTxFees(t *testing.T) { + feeTestsDefaultCfg := StaticConfig{ + TxFee: 1 * units.Avax, + CreateAssetTxFee: 2 * units.Avax, + CreateSubnetTxFee: 3 * units.Avax, + TransformSubnetTxFee: 4 * units.Avax, + CreateBlockchainTxFee: 5 * units.Avax, + AddPrimaryNetworkValidatorFee: 6 * units.Avax, + AddPrimaryNetworkDelegatorFee: 7 * units.Avax, + AddSubnetValidatorFee: 8 * units.Avax, + AddSubnetDelegatorFee: 9 * units.Avax, + } + + latestForkTime := time.Unix(1713945427, 0) + upgrades := upgrade.Config{ + EUpgradeTime: latestForkTime, + DurangoTime: latestForkTime.Add(-1 * time.Hour), + CortinaTime: latestForkTime.Add(-2 * time.Hour), + BanffTime: latestForkTime.Add(-3 * time.Hour), + ApricotPhase5Time: latestForkTime.Add(-4 * time.Hour), + ApricotPhase3Time: latestForkTime.Add(-5 * time.Hour), + } + + // chain times needed to have specific upgrades active + preEUpgradeTime := upgrades.EUpgradeTime.Add(-1 * time.Second) + preApricotPhase3Time := upgrades.ApricotPhase3Time.Add(-1 * time.Second) + + tests := []struct { + name string + chainTime time.Time + unsignedTx func() txs.UnsignedTx + expected uint64 + }{ + { + name: "AddValidatorTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: addValidatorTx, + expected: feeTestsDefaultCfg.AddPrimaryNetworkValidatorFee, + }, + { + name: "AddSubnetValidatorTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: addSubnetValidatorTx, + expected: feeTestsDefaultCfg.AddSubnetValidatorFee, + }, + { + name: "AddDelegatorTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: addDelegatorTx, + expected: feeTestsDefaultCfg.AddPrimaryNetworkDelegatorFee, + }, + { + name: "CreateChainTx pre ApricotPhase3", + chainTime: preApricotPhase3Time, + unsignedTx: createChainTx, + expected: feeTestsDefaultCfg.CreateAssetTxFee, + }, + { + name: "CreateChainTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: createChainTx, + expected: feeTestsDefaultCfg.CreateBlockchainTxFee, + }, + { + name: "CreateSubnetTx pre ApricotPhase3", + chainTime: preApricotPhase3Time, + unsignedTx: createSubnetTx, + expected: feeTestsDefaultCfg.CreateAssetTxFee, + }, + { + name: "CreateSubnetTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: createSubnetTx, + expected: feeTestsDefaultCfg.CreateSubnetTxFee, + }, + { + name: "RemoveSubnetValidatorTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: removeSubnetValidatorTx, + expected: feeTestsDefaultCfg.TxFee, + }, + { + name: "TransformSubnetTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: transformSubnetTx, + expected: feeTestsDefaultCfg.TransformSubnetTxFee, + }, + { + name: "TransferSubnetOwnershipTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: transferSubnetOwnershipTx, + expected: feeTestsDefaultCfg.TxFee, + }, + { + name: "AddPermissionlessValidatorTx Primary Network pre EUpgrade", + chainTime: upgrades.EUpgradeTime.Add(-1 * time.Second), + unsignedTx: func() txs.UnsignedTx { + return addPermissionlessValidatorTx(constants.PrimaryNetworkID) + }, + expected: feeTestsDefaultCfg.AddPrimaryNetworkValidatorFee, + }, + { + name: "AddPermissionlessValidatorTx Subnet pre EUpgrade", + chainTime: upgrades.EUpgradeTime.Add(-1 * time.Second), + unsignedTx: func() txs.UnsignedTx { + subnetID := ids.GenerateTestID() + require.NotEqual(t, constants.PrimaryNetworkID, subnetID) + return addPermissionlessValidatorTx(subnetID) + }, + expected: feeTestsDefaultCfg.AddSubnetValidatorFee, + }, + { + name: "AddPermissionlessDelegatorTx Primary Network pre EUpgrade", + chainTime: upgrades.EUpgradeTime.Add(-1 * time.Second), + unsignedTx: func() txs.UnsignedTx { + return addPermissionlessDelegatorTx(constants.PrimaryNetworkID) + }, + expected: feeTestsDefaultCfg.AddPrimaryNetworkDelegatorFee, + }, + { + name: "AddPermissionlessDelegatorTx pre EUpgrade", + chainTime: upgrades.EUpgradeTime.Add(-1 * time.Second), + unsignedTx: func() txs.UnsignedTx { + subnetID := ids.GenerateTestID() + require.NotEqual(t, constants.PrimaryNetworkID, subnetID) + return addPermissionlessDelegatorTx(subnetID) + }, + expected: feeTestsDefaultCfg.AddSubnetDelegatorFee, + }, + { + name: "BaseTx pre EUpgrade", + chainTime: preEUpgradeTime, + unsignedTx: baseTx, + 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, + }, + { + name: "RewardValidatorTx pre EUpgrade", + chainTime: upgrades.EUpgradeTime.Add(-1 * time.Second), + unsignedTx: func() txs.UnsignedTx { + return &txs.RewardValidatorTx{ + TxID: ids.GenerateTestID(), + } + }, + expected: 0, + }, + { + name: "AdvanceTimeTx pre EUpgrade", + chainTime: upgrades.EUpgradeTime.Add(-1 * time.Second), + unsignedTx: func() txs.UnsignedTx { + return &txs.AdvanceTimeTx{ + Time: uint64(time.Now().Unix()), + } + }, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + uTx := tt.unsignedTx() + fc := NewStaticCalculator(feeTestsDefaultCfg, upgrades) + require.Equal(t, tt.expected, fc.CalculateFee(uTx, tt.chainTime)) + }) + } +} + +func addValidatorTx() txs.UnsignedTx { + return &txs.AddValidatorTx{} +} + +func addSubnetValidatorTx() txs.UnsignedTx { + return &txs.AddSubnetValidatorTx{} +} + +func addDelegatorTx() txs.UnsignedTx { + return &txs.AddDelegatorTx{} +} + +func createChainTx() txs.UnsignedTx { + return &txs.CreateChainTx{} +} + +func createSubnetTx() txs.UnsignedTx { + return &txs.CreateSubnetTx{} +} + +func removeSubnetValidatorTx() txs.UnsignedTx { + return &txs.RemoveSubnetValidatorTx{} +} + +func transformSubnetTx() txs.UnsignedTx { + return &txs.TransformSubnetTx{} +} + +func transferSubnetOwnershipTx() txs.UnsignedTx { + return &txs.TransferSubnetOwnershipTx{} +} + +func addPermissionlessValidatorTx(subnetID ids.ID) txs.UnsignedTx { + return &txs.AddPermissionlessValidatorTx{ + Subnet: subnetID, + } +} + +func addPermissionlessDelegatorTx(subnetID ids.ID) txs.UnsignedTx { + return &txs.AddPermissionlessDelegatorTx{ + Subnet: subnetID, + } +} + +func baseTx() txs.UnsignedTx { + return &txs.BaseTx{} +} + +func importTx() txs.UnsignedTx { + return &txs.ImportTx{} +} + +func exportTx() txs.UnsignedTx { + return &txs.ExportTx{} +} diff --git a/vms/platformvm/txs/fee/static_config.go b/vms/platformvm/txs/fee/static_config.go new file mode 100644 index 000000000000..e03fb701806a --- /dev/null +++ b/vms/platformvm/txs/fee/static_config.go @@ -0,0 +1,33 @@ +// 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-state creating transaction + TxFee uint64 `json:"txFee"` + + // Fee that must be burned by every state creating transaction before AP3 + CreateAssetTxFee uint64 `json:"createAssetTxFee"` + + // Fee that must be burned by every subnet creating transaction after AP3 + CreateSubnetTxFee uint64 `json:"createSubnetTxFee"` + + // Fee that must be burned by every transform subnet transaction + TransformSubnetTxFee uint64 `json:"transformSubnetTxFee"` + + // Fee that must be burned by every blockchain creating transaction after AP3 + CreateBlockchainTxFee uint64 `json:"createBlockchainTxFee"` + + // Transaction fee for adding a primary network validator + AddPrimaryNetworkValidatorFee uint64 `json:"addPrimaryNetworkValidatorFee"` + + // Transaction fee for adding a primary network delegator + AddPrimaryNetworkDelegatorFee uint64 `json:"addPrimaryNetworkDelegatorFee"` + + // Transaction fee for adding a subnet validator + AddSubnetValidatorFee uint64 `json:"addSubnetValidatorFee"` + + // Transaction fee for adding a subnet delegator + AddSubnetDelegatorFee uint64 `json:"addSubnetDelegatorFee"` +} diff --git a/vms/platformvm/txs/txstest/context.go b/vms/platformvm/txs/txstest/context.go index 514a85e2bae7..ec2252a632e1 100644 --- a/vms/platformvm/txs/txstest/context.go +++ b/vms/platformvm/txs/txstest/context.go @@ -8,6 +8,8 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/vms/platformvm/config" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/wallet/chain/p/builder" ) @@ -16,16 +18,22 @@ func newContext( cfg *config.Config, timestamp time.Time, ) *builder.Context { + var ( + feeCalc = fee.NewStaticCalculator(cfg.StaticFeeConfig, cfg.UpgradeConfig) + createSubnetFee = feeCalc.CalculateFee(&txs.CreateSubnetTx{}, timestamp) + createChainFee = feeCalc.CalculateFee(&txs.CreateChainTx{}, timestamp) + ) + return &builder.Context{ NetworkID: ctx.NetworkID, AVAXAssetID: ctx.AVAXAssetID, - BaseTxFee: cfg.TxFee, - CreateSubnetTxFee: cfg.GetCreateSubnetTxFee(timestamp), - TransformSubnetTxFee: cfg.TransformSubnetTxFee, - CreateBlockchainTxFee: cfg.GetCreateBlockchainTxFee(timestamp), - AddPrimaryNetworkValidatorFee: cfg.AddPrimaryNetworkValidatorFee, - AddPrimaryNetworkDelegatorFee: cfg.AddPrimaryNetworkDelegatorFee, - AddSubnetValidatorFee: cfg.AddSubnetValidatorFee, - AddSubnetDelegatorFee: cfg.AddSubnetDelegatorFee, + BaseTxFee: cfg.StaticFeeConfig.TxFee, + CreateSubnetTxFee: createSubnetFee, + TransformSubnetTxFee: cfg.StaticFeeConfig.TransformSubnetTxFee, + CreateBlockchainTxFee: createChainFee, + AddPrimaryNetworkValidatorFee: cfg.StaticFeeConfig.AddPrimaryNetworkValidatorFee, + AddPrimaryNetworkDelegatorFee: cfg.StaticFeeConfig.AddPrimaryNetworkDelegatorFee, + AddSubnetValidatorFee: cfg.StaticFeeConfig.AddSubnetValidatorFee, + AddSubnetDelegatorFee: cfg.StaticFeeConfig.AddSubnetDelegatorFee, } } diff --git a/vms/platformvm/validator_set_property_test.go b/vms/platformvm/validator_set_property_test.go index 4c1e3ef9fe2c..faf5eb810d51 100644 --- a/vms/platformvm/validator_set_property_test.go +++ b/vms/platformvm/validator_set_property_test.go @@ -43,6 +43,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest" "github.com/ava-labs/avalanchego/vms/platformvm/upgrade" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -652,16 +653,18 @@ func buildVM(t *testing.T) (*VM, ids.ID, error) { UptimeLockedCalculator: uptime.NewLockedCalculator(), SybilProtectionEnabled: true, Validators: validators.NewManager(), - TxFee: defaultTxFee, - CreateSubnetTxFee: 100 * defaultTxFee, - TransformSubnetTxFee: 100 * defaultTxFee, - CreateBlockchainTxFee: 100 * defaultTxFee, - MinValidatorStake: defaultMinValidatorStake, - MaxValidatorStake: defaultMaxValidatorStake, - MinDelegatorStake: defaultMinDelegatorStake, - MinStakeDuration: defaultMinStakingDuration, - MaxStakeDuration: defaultMaxStakingDuration, - RewardConfig: defaultRewardConfig, + StaticFeeConfig: fee.StaticConfig{ + TxFee: defaultTxFee, + CreateSubnetTxFee: 100 * defaultTxFee, + TransformSubnetTxFee: 100 * defaultTxFee, + CreateBlockchainTxFee: 100 * defaultTxFee, + }, + MinValidatorStake: defaultMinValidatorStake, + MaxValidatorStake: defaultMaxValidatorStake, + MinDelegatorStake: defaultMinDelegatorStake, + MinStakeDuration: defaultMinStakingDuration, + MaxStakeDuration: defaultMaxStakingDuration, + RewardConfig: defaultRewardConfig, UpgradeConfig: upgrade.Config{ ApricotPhase3Time: forkTime, ApricotPhase5Time: forkTime, diff --git a/vms/platformvm/vm_regression_test.go b/vms/platformvm/vm_regression_test.go index 6813919614b8..d29b2b0af1fa 100644 --- a/vms/platformvm/vm_regression_test.go +++ b/vms/platformvm/vm_regression_test.go @@ -643,7 +643,7 @@ func TestRejectedStateRegressionInvalidValidatorTimestamp(t *testing.T) { ID: vm.ctx.AVAXAssetID, }, Out: &secp256k1fx.TransferOutput{ - Amt: vm.TxFee, + Amt: vm.StaticFeeConfig.TxFee, OutputOwners: secp256k1fx.OutputOwners{}, }, } @@ -660,7 +660,7 @@ func TestRejectedStateRegressionInvalidValidatorTimestamp(t *testing.T) { UTXOID: utxo.UTXOID, Asset: utxo.Asset, In: &secp256k1fx.TransferInput{ - Amt: vm.TxFee, + Amt: vm.StaticFeeConfig.TxFee, }, }, }, @@ -890,7 +890,7 @@ func TestRejectedStateRegressionInvalidValidatorReward(t *testing.T) { ID: vm.ctx.AVAXAssetID, }, Out: &secp256k1fx.TransferOutput{ - Amt: vm.TxFee, + Amt: vm.StaticFeeConfig.TxFee, OutputOwners: secp256k1fx.OutputOwners{}, }, } @@ -907,7 +907,7 @@ func TestRejectedStateRegressionInvalidValidatorReward(t *testing.T) { UTXOID: utxo.UTXOID, Asset: utxo.Asset, In: &secp256k1fx.TransferInput{ - Amt: vm.TxFee, + Amt: vm.StaticFeeConfig.TxFee, }, }, }, diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index 6f1177ed17f1..13802ad4dae2 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -57,6 +57,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/status" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest" "github.com/ava-labs/avalanchego/vms/platformvm/upgrade" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -245,16 +246,18 @@ func defaultVM(t *testing.T, f fork) (*VM, *txstest.Builder, database.Database, UptimeLockedCalculator: uptime.NewLockedCalculator(), SybilProtectionEnabled: true, Validators: validators.NewManager(), - TxFee: defaultTxFee, - CreateSubnetTxFee: 100 * defaultTxFee, - TransformSubnetTxFee: 100 * defaultTxFee, - CreateBlockchainTxFee: 100 * defaultTxFee, - MinValidatorStake: defaultMinValidatorStake, - MaxValidatorStake: defaultMaxValidatorStake, - MinDelegatorStake: defaultMinDelegatorStake, - MinStakeDuration: defaultMinStakingDuration, - MaxStakeDuration: defaultMaxStakingDuration, - RewardConfig: defaultRewardConfig, + StaticFeeConfig: fee.StaticConfig{ + TxFee: defaultTxFee, + CreateSubnetTxFee: 100 * defaultTxFee, + TransformSubnetTxFee: 100 * defaultTxFee, + CreateBlockchainTxFee: 100 * defaultTxFee, + }, + MinValidatorStake: defaultMinValidatorStake, + MaxValidatorStake: defaultMaxValidatorStake, + MinDelegatorStake: defaultMinDelegatorStake, + MinStakeDuration: defaultMinStakingDuration, + MaxStakeDuration: defaultMaxStakingDuration, + RewardConfig: defaultRewardConfig, UpgradeConfig: upgrade.Config{ ApricotPhase3Time: apricotPhase3Time, ApricotPhase5Time: apricotPhase5Time, @@ -387,7 +390,7 @@ func TestGenesis(t *testing.T) { require.NoError(err) require.Equal(utxo.Address, addr) - require.Equal(uint64(utxo.Amount)-vm.CreateSubnetTxFee, out.Amount()) + require.Equal(uint64(utxo.Amount)-vm.StaticFeeConfig.CreateSubnetTxFee, out.Amount()) } } @@ -2367,7 +2370,7 @@ func TestBaseTx(t *testing.T) { } require.Equal(totalOutputAmt, key0OutputAmt+key1OutputAmt+changeAddrOutputAmt) - require.Equal(vm.TxFee, totalInputAmt-totalOutputAmt) + require.Equal(vm.StaticFeeConfig.TxFee, totalInputAmt-totalOutputAmt) require.Equal(sendAmt, key1OutputAmt) vm.ctx.Lock.Unlock()