From f19a2756486bf144bde6d6c6091024f8ca2fbe62 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 6 Sep 2023 16:31:37 +0300 Subject: [PATCH 01/19] add equality tests --- precompile/contracts/feemanager/contract.abi | 182 +++++++++++++ precompile/contracts/feemanager/contract.go | 136 +++++++++- .../contracts/feemanager/contract_test.go | 241 ++++++++++++++++++ .../contracts/nativeminter/contract.abi | 91 +++++++ precompile/contracts/nativeminter/contract.go | 26 ++ .../contracts/nativeminter/contract_test.go | 77 ++++++ 6 files changed, 752 insertions(+), 1 deletion(-) create mode 100644 precompile/contracts/feemanager/contract.abi create mode 100644 precompile/contracts/nativeminter/contract.abi diff --git a/precompile/contracts/feemanager/contract.abi b/precompile/contracts/feemanager/contract.abi new file mode 100644 index 0000000000..c6b5049a82 --- /dev/null +++ b/precompile/contracts/feemanager/contract.abi @@ -0,0 +1,182 @@ +[ + { + "inputs": [], + "name": "getFeeConfig", + "outputs": [ + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "targetBlockRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minBaseFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "targetGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseFeeChangeDenominator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minBlockGasCost", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxBlockGasCost", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockGasCostStep", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getFeeConfigLastChangedAt", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "readAllowList", + "outputs": [ + { + "internalType": "uint256", + "name": "role", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "targetBlockRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minBaseFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "targetGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseFeeChangeDenominator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minBlockGasCost", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxBlockGasCost", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockGasCostStep", + "type": "uint256" + } + ], + "name": "setFeeConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setNone", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] diff --git a/precompile/contracts/feemanager/contract.go b/precompile/contracts/feemanager/contract.go index a4ecd9a4e4..6000336956 100644 --- a/precompile/contracts/feemanager/contract.go +++ b/precompile/contracts/feemanager/contract.go @@ -4,10 +4,12 @@ package feemanager import ( + _ "embed" "errors" "fmt" "math/big" + "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contract" @@ -50,8 +52,26 @@ var ( feeConfigLastChangedAtKey = common.Hash{'l', 'c', 'a'} ErrCannotChangeFee = errors.New("non-enabled cannot change fee config") + ErrInvalidLen = errors.New("invalid input length for fee config Input") + + // IFeeManagerV2RawABI contains the raw ABI of IFeeManagerV2 contract. + //go:embed contract.abi + IFeeManagerV2RawABI string + + IFeeManagerV2ABI = contract.ParseABI(IFeeManagerV2RawABI) ) +type GetFeeConfigOutput struct { + GasLimit *big.Int + TargetBlockRate *big.Int + MinBaseFee *big.Int + TargetGas *big.Int + BaseFeeChangeDenominator *big.Int + MinBlockGasCost *big.Int + MaxBlockGasCost *big.Int + BlockGasCostStep *big.Int +} + // GetFeeManagerStatus returns the role of [address] for the fee config manager list. func GetFeeManagerStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) @@ -112,7 +132,7 @@ func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]by // assumes that [input] does not include selector (omits first 4 bytes in PackSetFeeConfigInput) func UnpackFeeConfigInput(input []byte) (commontype.FeeConfig, error) { if len(input) != feeConfigInputLen { - return commontype.FeeConfig{}, fmt.Errorf("invalid input length for fee config Input: %d", len(input)) + return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) } feeConfig := commontype.FeeConfig{} for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { @@ -301,3 +321,117 @@ func createFeeManagerPrecompile() contract.StatefulPrecompiledContract { } return contract } + +// PackGetFeeConfig packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackGetFeeConfigV2() ([]byte, error) { + return IFeeManagerV2ABI.Pack("getFeeConfig") +} + +// PackGetFeeConfigOutput attempts to pack given [outputStruct] of type GetFeeConfigOutput +// to conform the ABI outputs. +func PackGetFeeConfigOutputV2(output commontype.FeeConfig) ([]byte, error) { + outputStruct := GetFeeConfigOutput{ + GasLimit: output.GasLimit, + TargetBlockRate: new(big.Int).SetUint64(output.TargetBlockRate), + MinBaseFee: output.MinBaseFee, + TargetGas: output.TargetGas, + BaseFeeChangeDenominator: output.BaseFeeChangeDenominator, + MinBlockGasCost: output.MinBlockGasCost, + MaxBlockGasCost: output.MaxBlockGasCost, + BlockGasCostStep: output.BlockGasCostStep, + } + return IFeeManagerV2ABI.PackOutput("getFeeConfig", + outputStruct.GasLimit, + outputStruct.TargetBlockRate, + outputStruct.MinBaseFee, + outputStruct.TargetGas, + outputStruct.BaseFeeChangeDenominator, + outputStruct.MinBlockGasCost, + outputStruct.MaxBlockGasCost, + outputStruct.BlockGasCostStep, + ) +} + +// UnpackGetFeeConfigOutput attempts to unpack [output] as GetFeeConfigOutput +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackGetFeeConfigOutputV2(output []byte) (commontype.FeeConfig, error) { + if len(output) != feeConfigInputLen { + return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(output)) + } + outputStruct := GetFeeConfigOutput{} + err := IFeeManagerV2ABI.UnpackIntoInterface(&outputStruct, "getFeeConfig", output) + + result := commontype.FeeConfig{ + GasLimit: outputStruct.GasLimit, + TargetBlockRate: outputStruct.TargetBlockRate.Uint64(), + MinBaseFee: outputStruct.MinBaseFee, + TargetGas: outputStruct.TargetGas, + BaseFeeChangeDenominator: outputStruct.BaseFeeChangeDenominator, + MinBlockGasCost: outputStruct.MinBlockGasCost, + MaxBlockGasCost: outputStruct.MaxBlockGasCost, + BlockGasCostStep: outputStruct.BlockGasCostStep, + } + return result, err +} + +// PackGetFeeConfigLastChangedAt packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackGetFeeConfigLastChangedAtV2() ([]byte, error) { + return IFeeManagerV2ABI.Pack("getFeeConfigLastChangedAt") +} + +// PackGetFeeConfigLastChangedAtOutput attempts to pack given blockNumber of type *big.Int +// to conform the ABI outputs. +func PackGetFeeConfigLastChangedAtOutputV2(blockNumber *big.Int) ([]byte, error) { + return IFeeManagerV2ABI.PackOutput("getFeeConfigLastChangedAt", blockNumber) +} + +// UnpackGetFeeConfigLastChangedAtOutput attempts to unpack given [output] into the *big.Int type output +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackGetFeeConfigLastChangedAtOutputV2(output []byte) (*big.Int, error) { + res, err := IFeeManagerV2ABI.Unpack("getFeeConfigLastChangedAt", output) + if err != nil { + return new(big.Int), err + } + unpacked := *abi.ConvertType(res[0], new(*big.Int)).(**big.Int) + return unpacked, nil +} + +// UnpackSetFeeConfigInput attempts to unpack [input] as SetFeeConfigInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackSetFeeConfigInputV2(input []byte) (commontype.FeeConfig, error) { + if len(input) != feeConfigInputLen { + return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) + } + inputStruct := GetFeeConfigOutput{} + err := IFeeManagerV2ABI.UnpackInputIntoInterface(&inputStruct, "setFeeConfig", input) + + result := commontype.FeeConfig{ + GasLimit: inputStruct.GasLimit, + TargetBlockRate: inputStruct.TargetBlockRate.Uint64(), + MinBaseFee: inputStruct.MinBaseFee, + TargetGas: inputStruct.TargetGas, + BaseFeeChangeDenominator: inputStruct.BaseFeeChangeDenominator, + MinBlockGasCost: inputStruct.MinBlockGasCost, + MaxBlockGasCost: inputStruct.MaxBlockGasCost, + BlockGasCostStep: inputStruct.BlockGasCostStep, + } + + return result, err +} + +// PackSetFeeConfig packs [inputStruct] of type SetFeeConfigInput into the appropriate arguments for setFeeConfig. +func PackSetFeeConfigV2(input commontype.FeeConfig) ([]byte, error) { + inputStruct := GetFeeConfigOutput{ + GasLimit: input.GasLimit, + TargetBlockRate: new(big.Int).SetUint64(input.TargetBlockRate), + MinBaseFee: input.MinBaseFee, + TargetGas: input.TargetGas, + BaseFeeChangeDenominator: input.BaseFeeChangeDenominator, + MinBlockGasCost: input.MinBlockGasCost, + MaxBlockGasCost: input.MaxBlockGasCost, + BlockGasCostStep: input.BlockGasCostStep, + } + return IFeeManagerV2ABI.Pack("setFeeConfig", inputStruct.GasLimit, inputStruct.TargetBlockRate, inputStruct.MinBaseFee, inputStruct.TargetGas, inputStruct.BaseFeeChangeDenominator, inputStruct.MinBlockGasCost, inputStruct.MaxBlockGasCost, inputStruct.BlockGasCostStep) +} diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index f88c52f89f..a4e1b16cd7 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -4,7 +4,9 @@ package feemanager import ( + "fmt" "math/big" + "math/rand" "testing" "github.com/ava-labs/subnet-evm/commontype" @@ -14,6 +16,7 @@ import ( "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) @@ -241,3 +244,241 @@ func TestFeeManager(t *testing.T) { func BenchmarkFeeManager(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } +func TestGetFeeConfig(t *testing.T) { + // Compare PackGetFeeConfigV2 vs PackGetFeeConfig + // to see if they are equivalent + + input := PackGetFeeConfigInput() + + input2, err := PackGetFeeConfigV2() + require.NoError(t, err) + + require.Equal(t, input, input2) +} + +func TestGetFeeConfigOutput(t *testing.T) { + // Compare PackFeeConfigV2 vs PackFeeConfig + // to see if they are equivalent + + for i := 0; i < 1000; i++ { + feeConfig := commontype.FeeConfig{ + GasLimit: big.NewInt(rand.Int63()), + TargetBlockRate: rand.Uint64(), + + MinBaseFee: big.NewInt(rand.Int63()), + TargetGas: big.NewInt(rand.Int63()), + BaseFeeChangeDenominator: big.NewInt(rand.Int63()), + + MinBlockGasCost: big.NewInt(rand.Int63()), + MaxBlockGasCost: big.NewInt(rand.Int63()), + BlockGasCostStep: big.NewInt(rand.Int63()), + } + + testGetFeeConfigOutput(t, feeConfig) + } + // Some edge cases + testGetFeeConfigOutput(t, testFeeConfig) + // These should panic + require.Panics(t, func() { + _, _ = PackGetFeeConfigOutputV2(commontype.FeeConfig{}) + }) + require.Panics(t, func() { + _, _ = PackFeeConfig(commontype.FeeConfig{}) + }) + + // These should err + unpacked, err := UnpackGetFeeConfigOutputV2([]byte{}) + require.ErrorIs(t, err, ErrInvalidLen) + + unpacked2, err := UnpackFeeConfigInput([]byte{}) + require.ErrorIs(t, err, ErrInvalidLen) + require.Equal(t, unpacked, unpacked2) +} + +func testGetFeeConfigOutput(t *testing.T, feeConfig commontype.FeeConfig) { + t.Helper() + t.Run(fmt.Sprintf("TestGetFeeConfigOutput, feeConfig %v", feeConfig), func(t *testing.T) { + // Test PackGetFeeConfigOutputV2, UnpackGetFeeConfigOutputV2 + input, err := PackGetFeeConfigOutputV2(feeConfig) + require.NoError(t, err) + + unpacked, err := UnpackGetFeeConfigOutputV2(input) + require.NoError(t, err) + + require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + + // Test PackGetFeeConfigOutput, UnpackGetFeeConfigOutput + input, err = PackFeeConfig(feeConfig) + require.NoError(t, err) + + unpacked, err = UnpackFeeConfigInput(input) + require.NoError(t, err) + + require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + + // // now mix and match + // Test PackGetFeeConfigOutput, PackGetFeeConfigOutputV2 + input, err = PackGetFeeConfigOutputV2(feeConfig) + require.NoError(t, err) + input2, err := PackFeeConfig(feeConfig) + require.NoError(t, err) + require.Equal(t, input, input2) + + // // Test UnpackGetFeeConfigOutput, UnpackGetFeeConfigOutputV2 + unpacked, err = UnpackGetFeeConfigOutputV2(input2) + require.NoError(t, err) + unpacked2, err := UnpackFeeConfigInput(input) + require.NoError(t, err) + require.True(t, unpacked.Equal(&unpacked2), "not equal: unpacked %v, unpacked2 %v", unpacked, unpacked2) + }) +} + +func TestGetLastChangedAtInput(t *testing.T) { + // Compare PackGetFeeConfigLastChangedAtV2 vs PackGetLastChangedAtInput + // to see if they are equivalent + + input := PackGetLastChangedAtInput() + + input2, err := PackGetFeeConfigLastChangedAtV2() + require.NoError(t, err) + + require.Equal(t, input, input2) +} + +func TestGetLastChangedAtOutput(t *testing.T) { + // Compare PackGetFeeConfigLastChangedAtOutputV2 vs PackGetLastChangedAtOutput + // to see if they are equivalent + + for i := 0; i < 1000; i++ { + lastChangedAt := big.NewInt(rand.Int63()) + testGetLastChangedAtOutput(t, lastChangedAt) + } + // Some edge cases + testGetLastChangedAtOutput(t, big.NewInt(0)) + testGetLastChangedAtOutput(t, big.NewInt(1)) + testGetLastChangedAtOutput(t, big.NewInt(2)) + testGetLastChangedAtOutput(t, math.MaxBig256) + testGetLastChangedAtOutput(t, math.MaxBig256.Sub(math.MaxBig256, common.Big1)) + testGetLastChangedAtOutput(t, math.MaxBig256.Add(math.MaxBig256, common.Big1)) +} + +func testGetLastChangedAtOutput(t *testing.T, lastChangedAt *big.Int) { + t.Helper() + t.Run(fmt.Sprintf("TestGetLastChangedAtOutput, lastChangedAt %v", lastChangedAt), func(t *testing.T) { + // Test PackGetFeeConfigLastChangedAtOutputV2, UnpackGetFeeConfigLastChangedAtOutputV2 + input, err := PackGetFeeConfigLastChangedAtOutputV2(lastChangedAt) + require.NoError(t, err) + + unpacked, err := UnpackGetFeeConfigLastChangedAtOutputV2(input) + require.NoError(t, err) + + require.Zero(t, lastChangedAt.Cmp(unpacked), "not equal: lastChangedAt %v, unpacked %v", lastChangedAt, unpacked) + + // Test PackGetLastChangedAtOutput, UnpackGetLastChangedAtOutput + input = common.BigToHash(lastChangedAt).Bytes() + + unpacked = common.BytesToHash(input).Big() + require.NoError(t, err) + + require.Zero(t, lastChangedAt.Cmp(unpacked), "not equal: lastChangedAt %v, unpacked %v", lastChangedAt) + + // now mix and match + // Test PackGetLastChangedAtOutput, PackGetFeeConfigLastChangedAtOutputV2 + input = common.BigToHash(lastChangedAt).Bytes() + require.NoError(t, err) + input2, err := PackGetFeeConfigLastChangedAtOutputV2(lastChangedAt) + require.NoError(t, err) + require.Equal(t, input, input2) + + // Test UnpackGetLastChangedAtOutput, UnpackGetFeeConfigLastChangedAtOutputV2 + unpacked = common.BytesToHash(input).Big() + require.NoError(t, err) + unpacked2, err := UnpackGetFeeConfigLastChangedAtOutputV2(input) + require.NoError(t, err) + require.EqualValues(t, unpacked, unpacked2) + }) +} + +func TestPackSetFeeConfigInput(t *testing.T) { + // Compare PackSetFeeConfigV2 vs PackSetFeeConfig + // to see if they are equivalent + for i := 0; i < 1000; i++ { + feeConfig := commontype.FeeConfig{ + GasLimit: big.NewInt(rand.Int63()), + TargetBlockRate: rand.Uint64(), + + MinBaseFee: big.NewInt(rand.Int63()), + TargetGas: big.NewInt(rand.Int63()), + BaseFeeChangeDenominator: big.NewInt(rand.Int63()), + + MinBlockGasCost: big.NewInt(rand.Int63()), + MaxBlockGasCost: big.NewInt(rand.Int63()), + BlockGasCostStep: big.NewInt(rand.Int63()), + } + + testPackSetFeeConfigInput(t, feeConfig) + } + // Some edge cases + // Some edge cases + testPackSetFeeConfigInput(t, testFeeConfig) + // These should panic + require.Panics(t, func() { + _, _ = PackSetFeeConfigV2(commontype.FeeConfig{}) + }) + require.Panics(t, func() { + _, _ = PackSetFeeConfig(commontype.FeeConfig{}) + }) + + // These should err + unpacked, err := UnpackSetFeeConfigInputV2([]byte{}) + require.ErrorIs(t, err, ErrInvalidLen) + + unpacked2, err := UnpackFeeConfigInput([]byte{}) + require.ErrorIs(t, err, ErrInvalidLen) + require.Equal(t, unpacked, unpacked2) +} + +func testPackSetFeeConfigInput(t *testing.T, feeConfig commontype.FeeConfig) { + t.Helper() + t.Run(fmt.Sprintf("TestPackSetFeeConfigInput, feeConfig %v", feeConfig), func(t *testing.T) { + // Test PackSetFeeConfigV2, UnpackSetFeeConfigInputV2 + input, err := PackSetFeeConfigV2(feeConfig) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + + unpacked, err := UnpackSetFeeConfigInputV2(input) + require.NoError(t, err) + + require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + + // Test PackSetFeeConfig, UnpackFeeConfigInput + input, err = PackSetFeeConfig(feeConfig) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + + unpacked, err = UnpackFeeConfigInput(input) + require.NoError(t, err) + + require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig) + + // now mix and match + // Test PackSetFeeConfig, PackSetFeeConfigV2 + input, err = PackSetFeeConfig(feeConfig) + require.NoError(t, err) + input2, err := PackSetFeeConfigV2(feeConfig) + require.NoError(t, err) + require.Equal(t, input, input2) + // exclude 4 bytes for function selector + input = input[4:] + input2 = input2[4:] + + // Test UnpackSetFeeConfigInputV2, UnpackFeeConfigInput + unpacked, err = UnpackSetFeeConfigInputV2(input2) + require.NoError(t, err) + unpacked2, err := UnpackFeeConfigInput(input) + require.NoError(t, err) + require.EqualValues(t, unpacked, unpacked2) + }) +} diff --git a/precompile/contracts/nativeminter/contract.abi b/precompile/contracts/nativeminter/contract.abi new file mode 100644 index 0000000000..ebb091259d --- /dev/null +++ b/precompile/contracts/nativeminter/contract.abi @@ -0,0 +1,91 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mintNativeCoin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "readAllowList", + "outputs": [ + { + "internalType": "uint256", + "name": "role", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setNone", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] diff --git a/precompile/contracts/nativeminter/contract.go b/precompile/contracts/nativeminter/contract.go index 63d6a624da..726de959f6 100644 --- a/precompile/contracts/nativeminter/contract.go +++ b/precompile/contracts/nativeminter/contract.go @@ -4,6 +4,7 @@ package nativeminter import ( + _ "embed" "errors" "fmt" "math/big" @@ -23,12 +24,23 @@ const ( MintGasCost = 30_000 ) +type MintNativeCoinInput struct { + Addr common.Address + Amount *big.Int +} + var ( // Singleton StatefulPrecompiledContract for minting native assets by permissioned callers. ContractNativeMinterPrecompile contract.StatefulPrecompiledContract = createNativeMinterPrecompile() mintSignature = contract.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount ErrCannotMint = errors.New("non-enabled cannot mint") + + // NativeMinterV2RawABI contains the raw ABI of NativeMinterV2 contract. + //go:embed contract.abi + NativeMinterV2RawABI string + + NativeMinterV2ABI = contract.ParseABI(NativeMinterV2RawABI) ) // GetContractNativeMinterStatus returns the role of [address] for the minter list. @@ -116,3 +128,17 @@ func createNativeMinterPrecompile() contract.StatefulPrecompiledContract { } return contract } + +// UnpackMintNativeCoinInput attempts to unpack [input] as MintNativeCoinInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackMintNativeCoinV2Input(input []byte) (MintNativeCoinInput, error) { + inputStruct := MintNativeCoinInput{} + err := NativeMinterV2ABI.UnpackInputIntoInterface(&inputStruct, "mintNativeCoin", input) + + return inputStruct, err +} + +// PackMintNativeCoin packs [inputStruct] of type MintNativeCoinInput into the appropriate arguments for mintNativeCoin. +func PackMintNativeCoinV2(inputStruct MintNativeCoinInput) ([]byte, error) { + return NativeMinterV2ABI.Pack("mintNativeCoin", inputStruct.Addr, inputStruct.Amount) +} diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index a45f26dc2c..9abd586312 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -4,8 +4,12 @@ package nativeminter import ( + "fmt" + "math/big" + "math/rand" "testing" + "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contract" @@ -13,6 +17,7 @@ import ( "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" ) @@ -151,3 +156,75 @@ func TestContractNativeMinterRun(t *testing.T) { func BenchmarkContractNativeMinter(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } + +func TestUnpackAndPacks(t *testing.T) { + // Compare UnpackMintNativeCoinV2Input, PackMintNativeCoinV2 vs + // PackMintInput, UnpackMintInput to see if they are equivalent + + // Test UnpackMintNativeCoinV2Input, PackMintNativeCoinV2 + // against PackMintInput, UnpackMintInput + // for 1000 random addresses and amounts + for i := 0; i < 1000; i++ { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + amount := new(big.Int).SetInt64(rand.Int63()) + testUnpackAndPacks(t, addr, amount) + } + + // Some edge cases + testUnpackAndPacks(t, common.Address{}, common.Big0) + testUnpackAndPacks(t, common.Address{}, common.Big1) + testUnpackAndPacks(t, common.Address{}, math.MaxBig256) + testUnpackAndPacks(t, common.Address{}, math.MaxBig256.Sub(math.MaxBig256, common.Big1)) + testUnpackAndPacks(t, common.Address{}, math.MaxBig256.Add(math.MaxBig256, common.Big1)) + testUnpackAndPacks(t, constants.BlackholeAddr, common.Big2) +} + +func testUnpackAndPacks(t *testing.T, addr common.Address, amount *big.Int) { + // Test PackMintNativeCoinV2, UnpackMintNativeCoinV2Input + t.Helper() + t.Run(fmt.Sprintf("TestUnpackAndPacks, addr: %s, amount: %s", addr.String(), amount.String()), func(t *testing.T) { + input, err := PackMintNativeCoinV2(MintNativeCoinInput{Addr: addr, Amount: amount}) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + + unpacked, err := UnpackMintNativeCoinV2Input(input) + require.NoError(t, err) + + require.EqualValues(t, addr, unpacked.Addr) + require.Equal(t, amount.Bytes(), unpacked.Amount.Bytes()) + + // Test PackMintInput, UnpackMintInput + input, err = PackMintInput(addr, amount) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + + to, assetAmount, err := UnpackMintInput(input) + require.NoError(t, err) + + require.Equal(t, addr, to) + require.Equal(t, amount.Bytes(), assetAmount.Bytes()) + + // now mix and match + // Test PackMintInput, PackMintNativeCoinV2 + input, err = PackMintInput(addr, amount) + // exclude 4 bytes for function selector + input = input[4:] + require.NoError(t, err) + input2, err := PackMintNativeCoinV2(MintNativeCoinInput{Addr: addr, Amount: amount}) + // exclude 4 bytes for function selector + input2 = input2[4:] + require.NoError(t, err) + require.Equal(t, input, input2) + + // Test UnpackMintInput, UnpackMintNativeCoinV2Input + to, assetAmount, err = UnpackMintInput(input) + require.NoError(t, err) + unpacked, err = UnpackMintNativeCoinV2Input(input2) + require.NoError(t, err) + require.Equal(t, to, unpacked.Addr) + require.Equal(t, assetAmount.Bytes(), unpacked.Amount.Bytes()) + }) +} From 143aeb7ca9525249c1cd4a36cc09efaffbf81643 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 6 Sep 2023 16:37:13 +0300 Subject: [PATCH 02/19] regenerate abis --- precompile/contracts/feemanager/contract.abi | 13 -- .../contracts/rewardmanager/contract.abi | 114 +++++++++++++++++- 2 files changed, 113 insertions(+), 14 deletions(-) diff --git a/precompile/contracts/feemanager/contract.abi b/precompile/contracts/feemanager/contract.abi index c6b5049a82..e32f94e09e 100644 --- a/precompile/contracts/feemanager/contract.abi +++ b/precompile/contracts/feemanager/contract.abi @@ -153,19 +153,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { diff --git a/precompile/contracts/rewardmanager/contract.abi b/precompile/contracts/rewardmanager/contract.abi index d21d5bdc6b..544767ec0b 100644 --- a/precompile/contracts/rewardmanager/contract.abi +++ b/precompile/contracts/rewardmanager/contract.abi @@ -1 +1,113 @@ -[{"inputs":[],"name":"allowFeeRecipients","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"areFeeRecipientsAllowed","outputs":[{"internalType":"bool","name":"isAllowed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRewardAddress","outputs":[{"internalType":"address","name":"rewardAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[ + { + "inputs": [], + "name": "allowFeeRecipients", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "areFeeRecipientsAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "isAllowed", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentRewardAddress", + "outputs": [ + { + "internalType": "address", + "name": "rewardAddress", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "disableRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "readAllowList", + "outputs": [ + { + "internalType": "uint256", + "name": "role", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setNone", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setRewardAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] From a6a90a0a68f2a1dbfca5134e50d03d76d1f84e28 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 11 Sep 2023 19:22:56 +0300 Subject: [PATCH 03/19] add extra padding tests --- precompile/contracts/feemanager/contract.go | 24 +++++++--- .../contracts/feemanager/contract_test.go | 46 ++++++++++++++++--- precompile/contracts/nativeminter/contract.go | 13 +++++- .../contracts/nativeminter/contract_test.go | 22 ++++++++- 4 files changed, 87 insertions(+), 18 deletions(-) diff --git a/precompile/contracts/feemanager/contract.go b/precompile/contracts/feemanager/contract.go index 6000336956..72b704a9c3 100644 --- a/precompile/contracts/feemanager/contract.go +++ b/precompile/contracts/feemanager/contract.go @@ -356,12 +356,13 @@ func PackGetFeeConfigOutputV2(output commontype.FeeConfig) ([]byte, error) { // UnpackGetFeeConfigOutput attempts to unpack [output] as GetFeeConfigOutput // assumes that [output] does not include selector (omits first 4 func signature bytes) func UnpackGetFeeConfigOutputV2(output []byte) (commontype.FeeConfig, error) { - if len(output) != feeConfigInputLen { - return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(output)) - } outputStruct := GetFeeConfigOutput{} err := IFeeManagerV2ABI.UnpackIntoInterface(&outputStruct, "getFeeConfig", output) + if err != nil { + return commontype.FeeConfig{}, err + } + result := commontype.FeeConfig{ GasLimit: outputStruct.GasLimit, TargetBlockRate: outputStruct.TargetBlockRate.Uint64(), @@ -372,7 +373,7 @@ func UnpackGetFeeConfigOutputV2(output []byte) (commontype.FeeConfig, error) { MaxBlockGasCost: outputStruct.MaxBlockGasCost, BlockGasCostStep: outputStruct.BlockGasCostStep, } - return result, err + return result, nil } // PackGetFeeConfigLastChangedAt packs the include selector (first 4 func signature bytes). @@ -400,13 +401,22 @@ func UnpackGetFeeConfigLastChangedAtOutputV2(output []byte) (*big.Int, error) { // UnpackSetFeeConfigInput attempts to unpack [input] as SetFeeConfigInput // assumes that [input] does not include selector (omits first 4 func signature bytes) -func UnpackSetFeeConfigInputV2(input []byte) (commontype.FeeConfig, error) { - if len(input) != feeConfigInputLen { +// if [doLenCheck] is true, it will return an error if the length of [input] is not [feeConfigInputLen] +func UnpackSetFeeConfigInputV2(input []byte, doLenCheck bool) (commontype.FeeConfig, error) { + // Initially we had this check to ensure that the input was the correct length. + // However solidity does not always pack the input to the correct length, and allows + // for extra padding bytes to be added to the end of the input. Therefore, we have removed + // this check with the DUpgrade. We still need to keep this check for backwards compatibility. + if doLenCheck && len(input) != feeConfigInputLen { return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) } inputStruct := GetFeeConfigOutput{} err := IFeeManagerV2ABI.UnpackInputIntoInterface(&inputStruct, "setFeeConfig", input) + if err != nil { + return commontype.FeeConfig{}, err + } + result := commontype.FeeConfig{ GasLimit: inputStruct.GasLimit, TargetBlockRate: inputStruct.TargetBlockRate.Uint64(), @@ -418,7 +428,7 @@ func UnpackSetFeeConfigInputV2(input []byte) (commontype.FeeConfig, error) { BlockGasCostStep: inputStruct.BlockGasCostStep, } - return result, err + return result, nil } // PackSetFeeConfig packs [inputStruct] of type SetFeeConfigInput into the appropriate arguments for setFeeConfig. diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index a4e1b16cd7..f77a2eb919 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -286,13 +286,26 @@ func TestGetFeeConfigOutput(t *testing.T) { _, _ = PackFeeConfig(commontype.FeeConfig{}) }) - // These should err + // These should unpacked, err := UnpackGetFeeConfigOutputV2([]byte{}) - require.ErrorIs(t, err, ErrInvalidLen) + require.Error(t, err) unpacked2, err := UnpackFeeConfigInput([]byte{}) require.ErrorIs(t, err, ErrInvalidLen) require.Equal(t, unpacked, unpacked2) + + // Test for extra padded bytes + input, err := PackGetFeeConfigOutputV2(testFeeConfig) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + // add extra padded bytes + input = append(input, make([]byte, 32)...) + _, err = UnpackFeeConfigInput(input) + require.ErrorIs(t, err, ErrInvalidLen) + + _, err = UnpackGetFeeConfigOutputV2(input) + require.Error(t, err) } func testGetFeeConfigOutput(t *testing.T, feeConfig commontype.FeeConfig) { @@ -430,12 +443,31 @@ func TestPackSetFeeConfigInput(t *testing.T) { }) // These should err - unpacked, err := UnpackSetFeeConfigInputV2([]byte{}) + _, err := UnpackSetFeeConfigInputV2([]byte{123}, true) require.ErrorIs(t, err, ErrInvalidLen) - unpacked2, err := UnpackFeeConfigInput([]byte{}) + _, err = UnpackSetFeeConfigInputV2([]byte{123}, false) + require.ErrorContains(t, err, "abi: improperly formatted input") + + _, err = UnpackFeeConfigInput([]byte{123}) require.ErrorIs(t, err, ErrInvalidLen) - require.Equal(t, unpacked, unpacked2) + + // Test for extra padded bytes + input, err := PackSetFeeConfigV2(testFeeConfig) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + // add extra padded bytes + input = append(input, make([]byte, 32)...) + _, err = UnpackFeeConfigInput(input) + require.ErrorIs(t, err, ErrInvalidLen) + + _, err = UnpackSetFeeConfigInputV2(input, true) + require.ErrorIs(t, err, ErrInvalidLen) + + unpacked, err := UnpackSetFeeConfigInputV2(input, false) + require.NoError(t, err) + require.True(t, testFeeConfig.Equal(&unpacked)) } func testPackSetFeeConfigInput(t *testing.T, feeConfig commontype.FeeConfig) { @@ -447,7 +479,7 @@ func testPackSetFeeConfigInput(t *testing.T, feeConfig commontype.FeeConfig) { // exclude 4 bytes for function selector input = input[4:] - unpacked, err := UnpackSetFeeConfigInputV2(input) + unpacked, err := UnpackSetFeeConfigInputV2(input, true) require.NoError(t, err) require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) @@ -475,7 +507,7 @@ func testPackSetFeeConfigInput(t *testing.T, feeConfig commontype.FeeConfig) { input2 = input2[4:] // Test UnpackSetFeeConfigInputV2, UnpackFeeConfigInput - unpacked, err = UnpackSetFeeConfigInputV2(input2) + unpacked, err = UnpackSetFeeConfigInputV2(input2, true) require.NoError(t, err) unpacked2, err := UnpackFeeConfigInput(input) require.NoError(t, err) diff --git a/precompile/contracts/nativeminter/contract.go b/precompile/contracts/nativeminter/contract.go index 726de959f6..f3da8119cb 100644 --- a/precompile/contracts/nativeminter/contract.go +++ b/precompile/contracts/nativeminter/contract.go @@ -35,6 +35,7 @@ var ( mintSignature = contract.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount ErrCannotMint = errors.New("non-enabled cannot mint") + ErrInvalidLen = errors.New("invalid input length for minting") // NativeMinterV2RawABI contains the raw ABI of NativeMinterV2 contract. //go:embed contract.abi @@ -71,7 +72,7 @@ func PackMintInput(address common.Address, amount *big.Int) ([]byte, error) { // assumes that [input] does not include selector (omits first 4 bytes in PackMintInput) func UnpackMintInput(input []byte) (common.Address, *big.Int, error) { if len(input) != mintInputLen { - return common.Address{}, nil, fmt.Errorf("invalid input length for minting: %d", len(input)) + return common.Address{}, nil, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) } to := common.BytesToAddress(contract.PackedHash(input, mintInputAddressSlot)) assetAmount := new(big.Int).SetBytes(contract.PackedHash(input, mintInputAmountSlot)) @@ -131,7 +132,15 @@ func createNativeMinterPrecompile() contract.StatefulPrecompiledContract { // UnpackMintNativeCoinInput attempts to unpack [input] as MintNativeCoinInput // assumes that [input] does not include selector (omits first 4 func signature bytes) -func UnpackMintNativeCoinV2Input(input []byte) (MintNativeCoinInput, error) { +// if [doLenCheck] is true, it will return an error if the length of [input] is not [mintInputLen] +func UnpackMintNativeCoinV2Input(input []byte, doLenCheck bool) (MintNativeCoinInput, error) { + // Initially we had this check to ensure that the input was the correct length. + // However solidity does not always pack the input to the correct length, and allows + // for extra padding bytes to be added to the end of the input. Therefore, we have removed + // this check with the DUpgrade. We still need to keep this check for backwards compatibility. + if doLenCheck && len(input) != mintInputLen { + return MintNativeCoinInput{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) + } inputStruct := MintNativeCoinInput{} err := NativeMinterV2ABI.UnpackInputIntoInterface(&inputStruct, "mintNativeCoin", input) diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index 9abd586312..cff0d87308 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -178,6 +178,24 @@ func TestUnpackAndPacks(t *testing.T) { testUnpackAndPacks(t, common.Address{}, math.MaxBig256.Sub(math.MaxBig256, common.Big1)) testUnpackAndPacks(t, common.Address{}, math.MaxBig256.Add(math.MaxBig256, common.Big1)) testUnpackAndPacks(t, constants.BlackholeAddr, common.Big2) + + input, err := PackMintNativeCoinV2(MintNativeCoinInput{Addr: constants.BlackholeAddr, Amount: common.Big2}) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + // add extra padded bytes + input = append(input, make([]byte, 32)...) + + _, _, err = UnpackMintInput(input) + require.ErrorIs(t, err, ErrInvalidLen) + + _, err = UnpackMintNativeCoinV2Input(input, true) + require.ErrorIs(t, err, ErrInvalidLen) + + unpacked, err := UnpackMintNativeCoinV2Input(input, false) + require.NoError(t, err) + require.Equal(t, constants.BlackholeAddr, unpacked.Addr) + require.Equal(t, common.Big2.Bytes(), unpacked.Amount.Bytes()) } func testUnpackAndPacks(t *testing.T, addr common.Address, amount *big.Int) { @@ -189,7 +207,7 @@ func testUnpackAndPacks(t *testing.T, addr common.Address, amount *big.Int) { // exclude 4 bytes for function selector input = input[4:] - unpacked, err := UnpackMintNativeCoinV2Input(input) + unpacked, err := UnpackMintNativeCoinV2Input(input, true) require.NoError(t, err) require.EqualValues(t, addr, unpacked.Addr) @@ -222,7 +240,7 @@ func testUnpackAndPacks(t *testing.T, addr common.Address, amount *big.Int) { // Test UnpackMintInput, UnpackMintNativeCoinV2Input to, assetAmount, err = UnpackMintInput(input) require.NoError(t, err) - unpacked, err = UnpackMintNativeCoinV2Input(input2) + unpacked, err = UnpackMintNativeCoinV2Input(input2, true) require.NoError(t, err) require.Equal(t, to, unpacked.Addr) require.Equal(t, assetAmount.Bytes(), unpacked.Amount.Bytes()) From 1166ad3f1e1d56a0b2a740a161e5cae77c451660 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 12 Sep 2023 18:29:16 +0300 Subject: [PATCH 04/19] override network upgrades (#839) * override network upgrades * genesis network upgrades defaults * fix conflicts * Update plugin/evm/vm.go From c31b56bc1ec989b82c6f1f86d40a0397f0c15af1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 13 Sep 2023 20:32:53 +0300 Subject: [PATCH 05/19] update native minter --- precompile/allowlist/allowlist.go | 6 +- precompile/contract/utils.go | 4 + precompile/contracts/nativeminter/config.go | 5 +- precompile/contracts/nativeminter/contract.go | 99 ++++++--------- .../contracts/nativeminter/contract_test.go | 113 ++---------------- precompile/contracts/nativeminter/module.go | 3 +- 6 files changed, 59 insertions(+), 171 deletions(-) diff --git a/precompile/allowlist/allowlist.go b/precompile/allowlist/allowlist.go index ec4f53c047..e0135ddc25 100644 --- a/precompile/allowlist/allowlist.go +++ b/precompile/allowlist/allowlist.go @@ -170,14 +170,10 @@ func CreateAllowListPrecompile(precompileAddr common.Address) contract.StatefulP func CreateAllowListFunctions(precompileAddr common.Address) []*contract.StatefulPrecompileFunction { setAdmin := contract.NewStatefulPrecompileFunction(setAdminSignature, createAllowListRoleSetter(precompileAddr, AdminRole)) - setManager := contract.NewStatefulPrecompileFunctionWithActivator(setManagerSignature, createAllowListRoleSetter(precompileAddr, ManagerRole), isManagerRoleActivated) + setManager := contract.NewStatefulPrecompileFunctionWithActivator(setManagerSignature, createAllowListRoleSetter(precompileAddr, ManagerRole), contract.IsDUpgradeActivated) setEnabled := contract.NewStatefulPrecompileFunction(setEnabledSignature, createAllowListRoleSetter(precompileAddr, EnabledRole)) setNone := contract.NewStatefulPrecompileFunction(setNoneSignature, createAllowListRoleSetter(precompileAddr, NoRole)) read := contract.NewStatefulPrecompileFunction(readAllowListSignature, createReadAllowList(precompileAddr)) return []*contract.StatefulPrecompileFunction{setAdmin, setManager, setEnabled, setNone, read} } - -func isManagerRoleActivated(evm contract.AccessibleState) bool { - return evm.GetChainConfig().IsDUpgrade(evm.GetBlockContext().Timestamp()) -} diff --git a/precompile/contract/utils.go b/precompile/contract/utils.go index b294a7adfe..c938481f93 100644 --- a/precompile/contract/utils.go +++ b/precompile/contract/utils.go @@ -88,3 +88,7 @@ func ParseABI(rawABI string) abi.ABI { return parsed } + +func IsDUpgradeActivated(evm AccessibleState) bool { + return evm.GetChainConfig().IsDUpgrade(evm.GetBlockContext().Timestamp()) +} diff --git a/precompile/contracts/nativeminter/config.go b/precompile/contracts/nativeminter/config.go index e9b358abfd..38a65ee6c8 100644 --- a/precompile/contracts/nativeminter/config.go +++ b/precompile/contracts/nativeminter/config.go @@ -16,7 +16,7 @@ import ( var _ precompileconfig.Config = &Config{} -// Config implements the StatefulPrecompileConfig interface while adding in the +// Config implements the precompileconfig.Config interface while adding in the // ContractNativeMinter specific precompile config. type Config struct { allowlist.AllowListConfig @@ -49,6 +49,9 @@ func NewDisableConfig(blockTimestamp *uint64) *Config { }, } } + +// Key returns the key for the ContractNativeMinter precompileconfig. +// This should be the same key as used in the precompile module. func (*Config) Key() string { return ConfigKey } // Equal returns true if [cfg] is a [*ContractNativeMinterConfig] and it has been configured identical to [c]. diff --git a/precompile/contracts/nativeminter/contract.go b/precompile/contracts/nativeminter/contract.go index f3da8119cb..7ecde8c507 100644 --- a/precompile/contracts/nativeminter/contract.go +++ b/precompile/contracts/nativeminter/contract.go @@ -16,9 +16,6 @@ import ( ) const ( - mintInputAddressSlot = iota - mintInputAmountSlot - mintInputLen = common.HashLength + common.HashLength MintGasCost = 30_000 @@ -33,15 +30,14 @@ var ( // Singleton StatefulPrecompiledContract for minting native assets by permissioned callers. ContractNativeMinterPrecompile contract.StatefulPrecompiledContract = createNativeMinterPrecompile() - mintSignature = contract.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount ErrCannotMint = errors.New("non-enabled cannot mint") ErrInvalidLen = errors.New("invalid input length for minting") - // NativeMinterV2RawABI contains the raw ABI of NativeMinterV2 contract. + // NativeMinterRawABI contains the raw ABI of NativeMinterV2 contract. //go:embed contract.abi - NativeMinterV2RawABI string + NativeMinterRawABI string - NativeMinterV2ABI = contract.ParseABI(NativeMinterV2RawABI) + NativeMinterABI = contract.ParseABI(NativeMinterRawABI) ) // GetContractNativeMinterStatus returns the role of [address] for the minter list. @@ -55,28 +51,26 @@ func SetContractNativeMinterStatus(stateDB contract.StateDB, address common.Addr allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) } -// PackMintInput packs [address] and [amount] into the appropriate arguments for minting operation. -// Assumes that [amount] can be represented by 32 bytes. -func PackMintInput(address common.Address, amount *big.Int) ([]byte, error) { - // function selector (4 bytes) + input(hash for address + hash for amount) - res := make([]byte, contract.SelectorLen+mintInputLen) - err := contract.PackOrderedHashesWithSelector(res, mintSignature, []common.Hash{ - address.Hash(), - common.BigToHash(amount), - }) - - return res, err -} - -// UnpackMintInput attempts to unpack [input] into the arguments to the mint precompile -// assumes that [input] does not include selector (omits first 4 bytes in PackMintInput) -func UnpackMintInput(input []byte) (common.Address, *big.Int, error) { - if len(input) != mintInputLen { +// UnpackMintNativeCoinInput attempts to unpack [input] as address and amount. +// assumes that [input] does not include selector (omits first 4 func signature bytes) +// if [doLenCheck] is true, it will return an error if the length of [input] is not [mintInputLen] +func UnpackMintNativeCoinInput(input []byte, doLenCheck bool) (common.Address, *big.Int, error) { + // Initially we had this check to ensure that the input was the correct length. + // However solidity does not always pack the input to the correct length, and allows + // for extra padding bytes to be added to the end of the input. Therefore, we have removed + // this check with the DUpgrade. We still need to keep this check for backwards compatibility. + if doLenCheck && len(input) != mintInputLen { return common.Address{}, nil, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) } - to := common.BytesToAddress(contract.PackedHash(input, mintInputAddressSlot)) - assetAmount := new(big.Int).SetBytes(contract.PackedHash(input, mintInputAmountSlot)) - return to, assetAmount, nil + inputStruct := MintNativeCoinInput{} + err := NativeMinterABI.UnpackInputIntoInterface(&inputStruct, "mintNativeCoin", input) + + return inputStruct.Addr, inputStruct.Amount, err +} + +// PackMintNativeCoin packs [address] and [amount] into the appropriate arguments for mintNativeCoin. +func PackMintNativeCoin(address common.Address, amount *big.Int) ([]byte, error) { + return NativeMinterABI.Pack("mintNativeCoin", address, amount) } // mintNativeCoin checks if the caller is permissioned for minting operation. @@ -89,8 +83,8 @@ func mintNativeCoin(accessibleState contract.AccessibleState, caller common.Addr if readOnly { return nil, remainingGas, vmerrs.ErrWriteProtection } - - to, amount, err := UnpackMintInput(input) + doCheckLen := contract.IsDUpgradeActivated(accessibleState) + to, amount, err := UnpackMintNativeCoinInput(input, doCheckLen) if err != nil { return nil, remainingGas, err } @@ -112,42 +106,27 @@ func mintNativeCoin(accessibleState contract.AccessibleState, caller common.Addr return []byte{}, remainingGas, nil } -// createNativeMinterPrecompile returns a StatefulPrecompiledContract for native coin minting. The precompile -// is accessed controlled by an allow list at [precompileAddr]. +// createNativeMinterPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. +// Access to the getters/setters is controlled by an allow list for ContractAddress. func createNativeMinterPrecompile() contract.StatefulPrecompiledContract { - enabledFuncs := allowlist.CreateAllowListFunctions(ContractAddress) + var functions []*contract.StatefulPrecompileFunction + functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) - mintFunc := contract.NewStatefulPrecompileFunction(mintSignature, mintNativeCoin) + abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ + "mintNativeCoin": mintNativeCoin, + } - enabledFuncs = append(enabledFuncs, mintFunc) + for name, function := range abiFunctionMap { + method, ok := NativeMinterABI.Methods[name] + if !ok { + panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) + } + functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) + } // Construct the contract with no fallback function. - contract, err := contract.NewStatefulPrecompileContract(nil, enabledFuncs) - // TODO: Change this to be returned as an error after refactoring this precompile - // to use the new precompile template. + statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) if err != nil { panic(err) } - return contract -} - -// UnpackMintNativeCoinInput attempts to unpack [input] as MintNativeCoinInput -// assumes that [input] does not include selector (omits first 4 func signature bytes) -// if [doLenCheck] is true, it will return an error if the length of [input] is not [mintInputLen] -func UnpackMintNativeCoinV2Input(input []byte, doLenCheck bool) (MintNativeCoinInput, error) { - // Initially we had this check to ensure that the input was the correct length. - // However solidity does not always pack the input to the correct length, and allows - // for extra padding bytes to be added to the end of the input. Therefore, we have removed - // this check with the DUpgrade. We still need to keep this check for backwards compatibility. - if doLenCheck && len(input) != mintInputLen { - return MintNativeCoinInput{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) - } - inputStruct := MintNativeCoinInput{} - err := NativeMinterV2ABI.UnpackInputIntoInterface(&inputStruct, "mintNativeCoin", input) - - return inputStruct, err -} - -// PackMintNativeCoin packs [inputStruct] of type MintNativeCoinInput into the appropriate arguments for mintNativeCoin. -func PackMintNativeCoinV2(inputStruct MintNativeCoinInput) ([]byte, error) { - return NativeMinterV2ABI.Pack("mintNativeCoin", inputStruct.Addr, inputStruct.Amount) + return statefulContract } diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index b6343c037d..40a5c994ac 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -4,12 +4,8 @@ package nativeminter import ( - "fmt" - "math/big" - "math/rand" "testing" - "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contract" @@ -17,7 +13,6 @@ import ( "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" ) @@ -26,7 +21,7 @@ var tests = map[string]testutils.PrecompileTest{ Caller: allowlist.TestNoRoleAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { - input, err := PackMintInput(allowlist.TestNoRoleAddr, common.Big1) + input, err := PackMintNativeCoin(allowlist.TestNoRoleAddr, common.Big1) require.NoError(t, err) return input @@ -39,7 +34,7 @@ var tests = map[string]testutils.PrecompileTest{ Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { - input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) require.NoError(t, err) return input @@ -67,7 +62,7 @@ var tests = map[string]testutils.PrecompileTest{ Caller: allowlist.TestManagerAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { - input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) require.NoError(t, err) return input @@ -83,7 +78,7 @@ var tests = map[string]testutils.PrecompileTest{ Caller: allowlist.TestAdminAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { - input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) + input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) require.NoError(t, err) return input @@ -99,7 +94,7 @@ var tests = map[string]testutils.PrecompileTest{ Caller: allowlist.TestAdminAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { - input, err := PackMintInput(allowlist.TestAdminAddr, math.MaxBig256) + input, err := PackMintNativeCoin(allowlist.TestAdminAddr, math.MaxBig256) require.NoError(t, err) return input @@ -115,7 +110,7 @@ var tests = map[string]testutils.PrecompileTest{ Caller: allowlist.TestNoRoleAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { - input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) + input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) require.NoError(t, err) return input @@ -128,7 +123,7 @@ var tests = map[string]testutils.PrecompileTest{ Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { - input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) require.NoError(t, err) return input @@ -141,7 +136,7 @@ var tests = map[string]testutils.PrecompileTest{ Caller: allowlist.TestAdminAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { - input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) + input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) require.NoError(t, err) return input @@ -154,7 +149,7 @@ var tests = map[string]testutils.PrecompileTest{ Caller: allowlist.TestAdminAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { - input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) require.NoError(t, err) return input @@ -172,93 +167,3 @@ func TestContractNativeMinterRun(t *testing.T) { func BenchmarkContractNativeMinter(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } - -func TestUnpackAndPacks(t *testing.T) { - // Compare UnpackMintNativeCoinV2Input, PackMintNativeCoinV2 vs - // PackMintInput, UnpackMintInput to see if they are equivalent - - // Test UnpackMintNativeCoinV2Input, PackMintNativeCoinV2 - // against PackMintInput, UnpackMintInput - // for 1000 random addresses and amounts - for i := 0; i < 1000; i++ { - key, _ := crypto.GenerateKey() - addr := crypto.PubkeyToAddress(key.PublicKey) - amount := new(big.Int).SetInt64(rand.Int63()) - testUnpackAndPacks(t, addr, amount) - } - - // Some edge cases - testUnpackAndPacks(t, common.Address{}, common.Big0) - testUnpackAndPacks(t, common.Address{}, common.Big1) - testUnpackAndPacks(t, common.Address{}, math.MaxBig256) - testUnpackAndPacks(t, common.Address{}, math.MaxBig256.Sub(math.MaxBig256, common.Big1)) - testUnpackAndPacks(t, common.Address{}, math.MaxBig256.Add(math.MaxBig256, common.Big1)) - testUnpackAndPacks(t, constants.BlackholeAddr, common.Big2) - - input, err := PackMintNativeCoinV2(MintNativeCoinInput{Addr: constants.BlackholeAddr, Amount: common.Big2}) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] - // add extra padded bytes - input = append(input, make([]byte, 32)...) - - _, _, err = UnpackMintInput(input) - require.ErrorIs(t, err, ErrInvalidLen) - - _, err = UnpackMintNativeCoinV2Input(input, true) - require.ErrorIs(t, err, ErrInvalidLen) - - unpacked, err := UnpackMintNativeCoinV2Input(input, false) - require.NoError(t, err) - require.Equal(t, constants.BlackholeAddr, unpacked.Addr) - require.Equal(t, common.Big2.Bytes(), unpacked.Amount.Bytes()) -} - -func testUnpackAndPacks(t *testing.T, addr common.Address, amount *big.Int) { - // Test PackMintNativeCoinV2, UnpackMintNativeCoinV2Input - t.Helper() - t.Run(fmt.Sprintf("TestUnpackAndPacks, addr: %s, amount: %s", addr.String(), amount.String()), func(t *testing.T) { - input, err := PackMintNativeCoinV2(MintNativeCoinInput{Addr: addr, Amount: amount}) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] - - unpacked, err := UnpackMintNativeCoinV2Input(input, true) - require.NoError(t, err) - - require.EqualValues(t, addr, unpacked.Addr) - require.Equal(t, amount.Bytes(), unpacked.Amount.Bytes()) - - // Test PackMintInput, UnpackMintInput - input, err = PackMintInput(addr, amount) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] - - to, assetAmount, err := UnpackMintInput(input) - require.NoError(t, err) - - require.Equal(t, addr, to) - require.Equal(t, amount.Bytes(), assetAmount.Bytes()) - - // now mix and match - // Test PackMintInput, PackMintNativeCoinV2 - input, err = PackMintInput(addr, amount) - // exclude 4 bytes for function selector - input = input[4:] - require.NoError(t, err) - input2, err := PackMintNativeCoinV2(MintNativeCoinInput{Addr: addr, Amount: amount}) - // exclude 4 bytes for function selector - input2 = input2[4:] - require.NoError(t, err) - require.Equal(t, input, input2) - - // Test UnpackMintInput, UnpackMintNativeCoinV2Input - to, assetAmount, err = UnpackMintInput(input) - require.NoError(t, err) - unpacked, err = UnpackMintNativeCoinV2Input(input2, true) - require.NoError(t, err) - require.Equal(t, to, unpacked.Addr) - require.Equal(t, assetAmount.Bytes(), unpacked.Amount.Bytes()) - }) -} diff --git a/precompile/contracts/nativeminter/module.go b/precompile/contracts/nativeminter/module.go index 9828f8d437..0fd352f607 100644 --- a/precompile/contracts/nativeminter/module.go +++ b/precompile/contracts/nativeminter/module.go @@ -15,12 +15,13 @@ import ( var _ contract.Configurator = &configurator{} -// ConfigKey is the key used in json config files to specify this precompile config. +// ConfigKey is the key used in json config files to specify this precompile precompileconfig. // must be unique across all precompiles. const ConfigKey = "contractNativeMinterConfig" var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000001") +// Module is the precompile module. It is used to register the precompile contract. var Module = modules.Module{ ConfigKey: ConfigKey, Address: ContractAddress, From 80e6d6e7cd3e775a879ef7dd7097284f1f754b8d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 14 Sep 2023 12:51:56 +0300 Subject: [PATCH 06/19] nativeminter nit test changes --- precompile/contracts/nativeminter/config_test.go | 11 ++++++++++- precompile/contracts/nativeminter/contract_test.go | 8 ++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/precompile/contracts/nativeminter/config_test.go b/precompile/contracts/nativeminter/config_test.go index c0f9958e36..91a6784e0b 100644 --- a/precompile/contracts/nativeminter/config_test.go +++ b/precompile/contracts/nativeminter/config_test.go @@ -20,6 +20,15 @@ func TestVerify(t *testing.T) { enableds := []common.Address{allowlist.TestEnabledAddr} managers := []common.Address{allowlist.TestManagerAddr} tests := map[string]testutils.ConfigVerifyTest{ + "valid config": { + Config: NewConfig(utils.NewUint64(3), admins, enableds, managers, nil), + ChainConfig: func() precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) + config.EXPECT().IsDUpgrade(gomock.Any()).Return(true).AnyTimes() + return config + }(), + ExpectedError: "", + }, "invalid allow list config in native minter allowlist": { Config: NewConfig(utils.NewUint64(3), admins, admins, nil, nil), ExpectedError: "cannot set address", @@ -67,7 +76,7 @@ func TestEqual(t *testing.T) { Other: precompileconfig.NewMockConfig(gomock.NewController(t)), Expected: false, }, - "different timestamps": { + "different timestamp": { Config: NewConfig(utils.NewUint64(3), admins, nil, nil, nil), Other: NewConfig(utils.NewUint64(4), admins, nil, nil, nil), Expected: false, diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index 40a5c994ac..044cdead02 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -17,7 +17,7 @@ import ( ) var tests = map[string]testutils.PrecompileTest{ - "mint funds from no role fails": { + "calling mintNativeCoin from NoRole should fail": { Caller: allowlist.TestNoRoleAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { @@ -30,7 +30,7 @@ var tests = map[string]testutils.PrecompileTest{ ReadOnly: false, ExpectedErr: ErrCannotMint.Error(), }, - "mint funds from enabled address": { + "calling mintNativeCoin from Enabled should succeed": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { @@ -58,7 +58,7 @@ var tests = map[string]testutils.PrecompileTest{ require.Equal(t, common.Big2, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") }, }, - "mint funds from manager role succeeds": { + "calling mintNativeCoin from Manager should succeed": { Caller: allowlist.TestManagerAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { @@ -74,7 +74,7 @@ var tests = map[string]testutils.PrecompileTest{ require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") }, }, - "mint funds from admin address": { + "calling mintNativeCoin from Admin should succeed": { Caller: allowlist.TestAdminAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { From 116b9817d8abbdc89bb5a4c8c748a033e4b8ea1c Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 14 Sep 2023 16:43:27 +0300 Subject: [PATCH 07/19] replace fee manager packers --- contracts/abis/IAllowList.abi | 1 + contracts/abis/IFeeManager.abi | 1 + contracts/abis/INativeMinter.abi | 1 + precompile/contract/utils.go | 37 -- precompile/contracts/feemanager/config.go | 3 + precompile/contracts/feemanager/contract.go | 311 ++++++--------- .../contracts/feemanager/contract_test.go | 364 ++++-------------- precompile/contracts/feemanager/module.go | 8 +- precompile/contracts/nativeminter/contract.go | 23 +- .../contracts/nativeminter/contract_test.go | 47 +++ precompile/testutils/test_precompile.go | 6 + 11 files changed, 280 insertions(+), 522 deletions(-) create mode 100644 contracts/abis/IAllowList.abi create mode 100644 contracts/abis/IFeeManager.abi create mode 100644 contracts/abis/INativeMinter.abi diff --git a/contracts/abis/IAllowList.abi b/contracts/abis/IAllowList.abi new file mode 100644 index 0000000000..1e11d4b265 --- /dev/null +++ b/contracts/abis/IAllowList.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contracts/abis/IFeeManager.abi b/contracts/abis/IFeeManager.abi new file mode 100644 index 0000000000..0e4d5f28d7 --- /dev/null +++ b/contracts/abis/IFeeManager.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"getFeeConfig","outputs":[{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"targetBlockRate","type":"uint256"},{"internalType":"uint256","name":"minBaseFee","type":"uint256"},{"internalType":"uint256","name":"targetGas","type":"uint256"},{"internalType":"uint256","name":"baseFeeChangeDenominator","type":"uint256"},{"internalType":"uint256","name":"minBlockGasCost","type":"uint256"},{"internalType":"uint256","name":"maxBlockGasCost","type":"uint256"},{"internalType":"uint256","name":"blockGasCostStep","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFeeConfigLastChangedAt","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"targetBlockRate","type":"uint256"},{"internalType":"uint256","name":"minBaseFee","type":"uint256"},{"internalType":"uint256","name":"targetGas","type":"uint256"},{"internalType":"uint256","name":"baseFeeChangeDenominator","type":"uint256"},{"internalType":"uint256","name":"minBlockGasCost","type":"uint256"},{"internalType":"uint256","name":"maxBlockGasCost","type":"uint256"},{"internalType":"uint256","name":"blockGasCostStep","type":"uint256"}],"name":"setFeeConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contracts/abis/INativeMinter.abi b/contracts/abis/INativeMinter.abi new file mode 100644 index 0000000000..ba534bbf66 --- /dev/null +++ b/contracts/abis/INativeMinter.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mintNativeCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompile/contract/utils.go b/precompile/contract/utils.go index c938481f93..a61edc394f 100644 --- a/precompile/contract/utils.go +++ b/precompile/contract/utils.go @@ -10,7 +10,6 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -42,42 +41,6 @@ func DeductGas(suppliedGas uint64, requiredGas uint64) (uint64, error) { return suppliedGas - requiredGas, nil } -// PackOrderedHashesWithSelector packs the function selector and ordered list of hashes into [dst] -// byte slice. -// assumes that [dst] has sufficient room for [functionSelector] and [hashes]. -func PackOrderedHashesWithSelector(dst []byte, functionSelector []byte, hashes []common.Hash) error { - copy(dst[:len(functionSelector)], functionSelector) - return PackOrderedHashes(dst[len(functionSelector):], hashes) -} - -// PackOrderedHashes packs the ordered list of [hashes] into the [dst] byte buffer. -// assumes that [dst] has sufficient space to pack [hashes] or else this function will panic. -func PackOrderedHashes(dst []byte, hashes []common.Hash) error { - if len(dst) != len(hashes)*common.HashLength { - return fmt.Errorf("destination byte buffer has insufficient length (%d) for %d hashes", len(dst), len(hashes)) - } - - var ( - start = 0 - end = common.HashLength - ) - for _, hash := range hashes { - copy(dst[start:end], hash.Bytes()) - start += common.HashLength - end += common.HashLength - } - return nil -} - -// PackedHash returns packed the byte slice with common.HashLength from [packed] -// at the given [index]. -// Assumes that [packed] is composed entirely of packed 32 byte segments. -func PackedHash(packed []byte, index int) []byte { - start := common.HashLength * index - end := start + common.HashLength - return packed[start:end] -} - // ParseABI parses the given ABI string and returns the parsed ABI. // If the ABI is invalid, it panics. func ParseABI(rawABI string) abi.ABI { diff --git a/precompile/contracts/feemanager/config.go b/precompile/contracts/feemanager/config.go index 50dd7305ed..9dcfc307d2 100644 --- a/precompile/contracts/feemanager/config.go +++ b/precompile/contracts/feemanager/config.go @@ -46,6 +46,8 @@ func NewDisableConfig(blockTimestamp *uint64) *Config { } } +// Key returns the key for the FeeManager precompileconfig. +// This should be the same key as used in the precompile module. func (*Config) Key() string { return ConfigKey } // Equal returns true if [cfg] is a [*FeeManagerConfig] and it has been configured identical to [c]. @@ -67,6 +69,7 @@ func (c *Config) Equal(cfg precompileconfig.Config) bool { return c.InitialFeeConfig.Equal(other.InitialFeeConfig) } +// Verify tries to verify Config and returns an error accordingly. func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { if err := c.AllowListConfig.Verify(chainConfig, c.Upgrade); err != nil { return err diff --git a/precompile/contracts/feemanager/contract.go b/precompile/contracts/feemanager/contract.go index 1dc89b3a1c..5f5df58b17 100644 --- a/precompile/contracts/feemanager/contract.go +++ b/precompile/contracts/feemanager/contract.go @@ -45,23 +45,20 @@ var ( // Singleton StatefulPrecompiledContract for setting fee configs by permissioned callers. FeeManagerPrecompile contract.StatefulPrecompiledContract = createFeeManagerPrecompile() - setFeeConfigSignature = contract.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") - getFeeConfigSignature = contract.CalculateFunctionSelector("getFeeConfig()") - getFeeConfigLastChangedAtSignature = contract.CalculateFunctionSelector("getFeeConfigLastChangedAt()") - feeConfigLastChangedAtKey = common.Hash{'l', 'c', 'a'} ErrCannotChangeFee = errors.New("non-enabled cannot change fee config") ErrInvalidLen = errors.New("invalid input length for fee config Input") - // IFeeManagerV2RawABI contains the raw ABI of IFeeManagerV2 contract. + // IFeeManagerRawABI contains the raw ABI of FeeManager contract. //go:embed contract.abi - IFeeManagerV2RawABI string + FeeManagerRawABI string - IFeeManagerV2ABI = contract.ParseABI(IFeeManagerV2RawABI) + FeeManagerABI = contract.ParseABI(FeeManagerRawABI) ) -type GetFeeConfigOutput struct { +// FeeConfigABIStruct is the ABI struct for FeeConfig type. +type FeeConfigABIStruct struct { GasLimit *big.Int TargetBlockRate *big.Int MinBaseFee *big.Int @@ -83,86 +80,6 @@ func SetFeeManagerStatus(stateDB contract.StateDB, address common.Address, role allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) } -// PackGetFeeConfigInput packs the getFeeConfig signature -func PackGetFeeConfigInput() []byte { - return getFeeConfigSignature -} - -// PackGetLastChangedAtInput packs the getFeeConfigLastChangedAt signature -func PackGetLastChangedAtInput() []byte { - return getFeeConfigLastChangedAtSignature -} - -// PackFeeConfig packs [feeConfig] without the selector into the appropriate arguments for fee config operations. -func PackFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { - // input(feeConfig) - return packFeeConfigHelper(feeConfig, false) -} - -// PackSetFeeConfig packs [feeConfig] with the selector into the appropriate arguments for setting fee config operations. -func PackSetFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { - // function selector (4 bytes) + input(feeConfig) - return packFeeConfigHelper(feeConfig, true) -} - -func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]byte, error) { - hashes := []common.Hash{ - common.BigToHash(feeConfig.GasLimit), - common.BigToHash(new(big.Int).SetUint64(feeConfig.TargetBlockRate)), - common.BigToHash(feeConfig.MinBaseFee), - common.BigToHash(feeConfig.TargetGas), - common.BigToHash(feeConfig.BaseFeeChangeDenominator), - common.BigToHash(feeConfig.MinBlockGasCost), - common.BigToHash(feeConfig.MaxBlockGasCost), - common.BigToHash(feeConfig.BlockGasCostStep), - } - - if useSelector { - res := make([]byte, len(setFeeConfigSignature)+feeConfigInputLen) - err := contract.PackOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) - return res, err - } - - res := make([]byte, len(hashes)*common.HashLength) - err := contract.PackOrderedHashes(res, hashes) - return res, err -} - -// UnpackFeeConfigInput attempts to unpack [input] into the arguments to the fee config precompile -// assumes that [input] does not include selector (omits first 4 bytes in PackSetFeeConfigInput) -func UnpackFeeConfigInput(input []byte) (commontype.FeeConfig, error) { - if len(input) != feeConfigInputLen { - return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) - } - feeConfig := commontype.FeeConfig{} - for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { - listIndex := i - 1 - packedElement := contract.PackedHash(input, listIndex) - switch i { - case gasLimitKey: - feeConfig.GasLimit = new(big.Int).SetBytes(packedElement) - case targetBlockRateKey: - feeConfig.TargetBlockRate = new(big.Int).SetBytes(packedElement).Uint64() - case minBaseFeeKey: - feeConfig.MinBaseFee = new(big.Int).SetBytes(packedElement) - case targetGasKey: - feeConfig.TargetGas = new(big.Int).SetBytes(packedElement) - case baseFeeChangeDenominatorKey: - feeConfig.BaseFeeChangeDenominator = new(big.Int).SetBytes(packedElement) - case minBlockGasCostKey: - feeConfig.MinBlockGasCost = new(big.Int).SetBytes(packedElement) - case maxBlockGasCostKey: - feeConfig.MaxBlockGasCost = new(big.Int).SetBytes(packedElement) - case blockGasCostStepKey: - feeConfig.BlockGasCostStep = new(big.Int).SetBytes(packedElement) - default: - // This should never encounter an unknown fee config key - panic(fmt.Sprintf("unknown fee config key: %d", i)) - } - } - return feeConfig, nil -} - // GetStoredFeeConfig returns fee config from contract storage in given state func GetStoredFeeConfig(stateDB contract.StateDB) commontype.FeeConfig { feeConfig := commontype.FeeConfig{} @@ -239,6 +156,53 @@ func StoreFeeConfig(stateDB contract.StateDB, feeConfig commontype.FeeConfig, bl return nil } +// PackSetFeeConfig packs [inputStruct] of type SetFeeConfigInput into the appropriate arguments for setFeeConfig. +func PackSetFeeConfig(input commontype.FeeConfig) ([]byte, error) { + inputStruct := FeeConfigABIStruct{ + GasLimit: input.GasLimit, + TargetBlockRate: new(big.Int).SetUint64(input.TargetBlockRate), + MinBaseFee: input.MinBaseFee, + TargetGas: input.TargetGas, + BaseFeeChangeDenominator: input.BaseFeeChangeDenominator, + MinBlockGasCost: input.MinBlockGasCost, + MaxBlockGasCost: input.MaxBlockGasCost, + BlockGasCostStep: input.BlockGasCostStep, + } + return FeeManagerABI.Pack("setFeeConfig", inputStruct.GasLimit, inputStruct.TargetBlockRate, inputStruct.MinBaseFee, inputStruct.TargetGas, inputStruct.BaseFeeChangeDenominator, inputStruct.MinBlockGasCost, inputStruct.MaxBlockGasCost, inputStruct.BlockGasCostStep) +} + +// UnpackSetFeeConfigInput attempts to unpack [input] as SetFeeConfigInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +// if [skipLenCheck] is false, it will return an error if the length of [input] is not [feeConfigInputLen] +func UnpackSetFeeConfigInput(input []byte, skipLenCheck bool) (commontype.FeeConfig, error) { + // Initially we had this check to ensure that the input was the correct length. + // However solidity does not always pack the input to the correct length, and allows + // for extra padding bytes to be added to the end of the input. Therefore, we have removed + // this check with the DUpgrade. We still need to keep this check for backwards compatibility. + if !skipLenCheck && len(input) != feeConfigInputLen { + return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) + } + inputStruct := FeeConfigABIStruct{} + err := FeeManagerABI.UnpackInputIntoInterface(&inputStruct, "setFeeConfig", input) + + if err != nil { + return commontype.FeeConfig{}, err + } + + result := commontype.FeeConfig{ + GasLimit: inputStruct.GasLimit, + TargetBlockRate: inputStruct.TargetBlockRate.Uint64(), + MinBaseFee: inputStruct.MinBaseFee, + TargetGas: inputStruct.TargetGas, + BaseFeeChangeDenominator: inputStruct.BaseFeeChangeDenominator, + MinBlockGasCost: inputStruct.MinBlockGasCost, + MaxBlockGasCost: inputStruct.MaxBlockGasCost, + BlockGasCostStep: inputStruct.BlockGasCostStep, + } + + return result, nil +} + // setFeeConfig checks if the caller has permissions to set the fee config. // The execution function parses [input] into FeeConfig structure and sets contract storage accordingly. func setFeeConfig(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { @@ -250,7 +214,8 @@ func setFeeConfig(accessibleState contract.AccessibleState, caller common.Addres return nil, remainingGas, vmerrs.ErrWriteProtection } - feeConfig, err := UnpackFeeConfigInput(input) + skipLenCheck := contract.IsDUpgradeActivated(accessibleState) + feeConfig, err := UnpackSetFeeConfigInput(input, skipLenCheck) if err != nil { return nil, remainingGas, err } @@ -270,68 +235,16 @@ func setFeeConfig(accessibleState contract.AccessibleState, caller common.Addres return []byte{}, remainingGas, nil } -// getFeeConfig returns the stored fee config as an output. -// The execution function reads the contract state for the stored fee config and returns the output. -func getFeeConfig(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, GetFeeConfigGasCost); err != nil { - return nil, 0, err - } - - feeConfig := GetStoredFeeConfig(accessibleState.GetStateDB()) - - output, err := PackFeeConfig(feeConfig) - if err != nil { - return nil, remainingGas, err - } - - // Return the fee config as output and the remaining gas - return output, remainingGas, err -} - -// getFeeConfigLastChangedAt returns the block number that fee config was last changed in. -// The execution function reads the contract state for the stored block number and returns the output. -func getFeeConfigLastChangedAt(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, GetLastChangedAtGasCost); err != nil { - return nil, 0, err - } - - lastChangedAt := GetFeeConfigLastChangedAt(accessibleState.GetStateDB()) - - // Return an empty output and the remaining gas - return common.BigToHash(lastChangedAt).Bytes(), remainingGas, err -} - -// createFeeManagerPrecompile returns a StatefulPrecompiledContract -// with getters and setters for the chain's fee config. Access to the getters/setters -// is controlled by an allow list for ContractAddress. -func createFeeManagerPrecompile() contract.StatefulPrecompiledContract { - feeManagerFunctions := allowlist.CreateAllowListFunctions(ContractAddress) - - setFeeConfigFunc := contract.NewStatefulPrecompileFunction(setFeeConfigSignature, setFeeConfig) - getFeeConfigFunc := contract.NewStatefulPrecompileFunction(getFeeConfigSignature, getFeeConfig) - getFeeConfigLastChangedAtFunc := contract.NewStatefulPrecompileFunction(getFeeConfigLastChangedAtSignature, getFeeConfigLastChangedAt) - - feeManagerFunctions = append(feeManagerFunctions, setFeeConfigFunc, getFeeConfigFunc, getFeeConfigLastChangedAtFunc) - // Construct the contract with no fallback function. - contract, err := contract.NewStatefulPrecompileContract(nil, feeManagerFunctions) - // TODO Change this to be returned as an error after refactoring this precompile - // to use the new precompile template. - if err != nil { - panic(err) - } - return contract -} - // PackGetFeeConfig packs the include selector (first 4 func signature bytes). // This function is mostly used for tests. -func PackGetFeeConfigV2() ([]byte, error) { - return IFeeManagerV2ABI.Pack("getFeeConfig") +func PackGetFeeConfig() ([]byte, error) { + return FeeManagerABI.Pack("getFeeConfig") } // PackGetFeeConfigOutput attempts to pack given [outputStruct] of type GetFeeConfigOutput // to conform the ABI outputs. -func PackGetFeeConfigOutputV2(output commontype.FeeConfig) ([]byte, error) { - outputStruct := GetFeeConfigOutput{ +func PackGetFeeConfigOutput(output commontype.FeeConfig) ([]byte, error) { + outputStruct := FeeConfigABIStruct{ GasLimit: output.GasLimit, TargetBlockRate: new(big.Int).SetUint64(output.TargetBlockRate), MinBaseFee: output.MinBaseFee, @@ -341,7 +254,7 @@ func PackGetFeeConfigOutputV2(output commontype.FeeConfig) ([]byte, error) { MaxBlockGasCost: output.MaxBlockGasCost, BlockGasCostStep: output.BlockGasCostStep, } - return IFeeManagerV2ABI.PackOutput("getFeeConfig", + return FeeManagerABI.PackOutput("getFeeConfig", outputStruct.GasLimit, outputStruct.TargetBlockRate, outputStruct.MinBaseFee, @@ -355,9 +268,9 @@ func PackGetFeeConfigOutputV2(output commontype.FeeConfig) ([]byte, error) { // UnpackGetFeeConfigOutput attempts to unpack [output] as GetFeeConfigOutput // assumes that [output] does not include selector (omits first 4 func signature bytes) -func UnpackGetFeeConfigOutputV2(output []byte) (commontype.FeeConfig, error) { - outputStruct := GetFeeConfigOutput{} - err := IFeeManagerV2ABI.UnpackIntoInterface(&outputStruct, "getFeeConfig", output) +func UnpackGetFeeConfigOutput(output []byte) (commontype.FeeConfig, error) { + outputStruct := FeeConfigABIStruct{} + err := FeeManagerABI.UnpackIntoInterface(&outputStruct, "getFeeConfig", output) if err != nil { return commontype.FeeConfig{}, err @@ -376,22 +289,40 @@ func UnpackGetFeeConfigOutputV2(output []byte) (commontype.FeeConfig, error) { return result, nil } +// getFeeConfig returns the stored fee config as an output. +// The execution function reads the contract state for the stored fee config and returns the output. +func getFeeConfig(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetFeeConfigGasCost); err != nil { + return nil, 0, err + } + + feeConfig := GetStoredFeeConfig(accessibleState.GetStateDB()) + + output, err := PackGetFeeConfigOutput(feeConfig) + if err != nil { + return nil, remainingGas, err + } + + // Return the fee config as output and the remaining gas + return output, remainingGas, err +} + // PackGetFeeConfigLastChangedAt packs the include selector (first 4 func signature bytes). // This function is mostly used for tests. -func PackGetFeeConfigLastChangedAtV2() ([]byte, error) { - return IFeeManagerV2ABI.Pack("getFeeConfigLastChangedAt") +func PackGetFeeConfigLastChangedAt() ([]byte, error) { + return FeeManagerABI.Pack("getFeeConfigLastChangedAt") } // PackGetFeeConfigLastChangedAtOutput attempts to pack given blockNumber of type *big.Int // to conform the ABI outputs. -func PackGetFeeConfigLastChangedAtOutputV2(blockNumber *big.Int) ([]byte, error) { - return IFeeManagerV2ABI.PackOutput("getFeeConfigLastChangedAt", blockNumber) +func PackGetFeeConfigLastChangedAtOutput(blockNumber *big.Int) ([]byte, error) { + return FeeManagerABI.PackOutput("getFeeConfigLastChangedAt", blockNumber) } // UnpackGetFeeConfigLastChangedAtOutput attempts to unpack given [output] into the *big.Int type output // assumes that [output] does not include selector (omits first 4 func signature bytes) -func UnpackGetFeeConfigLastChangedAtOutputV2(output []byte) (*big.Int, error) { - res, err := IFeeManagerV2ABI.Unpack("getFeeConfigLastChangedAt", output) +func UnpackGetFeeConfigLastChangedAtOutput(output []byte) (*big.Int, error) { + res, err := FeeManagerABI.Unpack("getFeeConfigLastChangedAt", output) if err != nil { return new(big.Int), err } @@ -399,49 +330,45 @@ func UnpackGetFeeConfigLastChangedAtOutputV2(output []byte) (*big.Int, error) { return unpacked, nil } -// UnpackSetFeeConfigInput attempts to unpack [input] as SetFeeConfigInput -// assumes that [input] does not include selector (omits first 4 func signature bytes) -// if [doLenCheck] is true, it will return an error if the length of [input] is not [feeConfigInputLen] -func UnpackSetFeeConfigInputV2(input []byte, doLenCheck bool) (commontype.FeeConfig, error) { - // Initially we had this check to ensure that the input was the correct length. - // However solidity does not always pack the input to the correct length, and allows - // for extra padding bytes to be added to the end of the input. Therefore, we have removed - // this check with the DUpgrade. We still need to keep this check for backwards compatibility. - if doLenCheck && len(input) != feeConfigInputLen { - return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) +// getFeeConfigLastChangedAt returns the block number that fee config was last changed in. +// The execution function reads the contract state for the stored block number and returns the output. +func getFeeConfigLastChangedAt(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetLastChangedAtGasCost); err != nil { + return nil, 0, err } - inputStruct := GetFeeConfigOutput{} - err := IFeeManagerV2ABI.UnpackInputIntoInterface(&inputStruct, "setFeeConfig", input) + lastChangedAt := GetFeeConfigLastChangedAt(accessibleState.GetStateDB()) + packedOutput, err := PackGetFeeConfigLastChangedAtOutput(lastChangedAt) if err != nil { - return commontype.FeeConfig{}, err - } - - result := commontype.FeeConfig{ - GasLimit: inputStruct.GasLimit, - TargetBlockRate: inputStruct.TargetBlockRate.Uint64(), - MinBaseFee: inputStruct.MinBaseFee, - TargetGas: inputStruct.TargetGas, - BaseFeeChangeDenominator: inputStruct.BaseFeeChangeDenominator, - MinBlockGasCost: inputStruct.MinBlockGasCost, - MaxBlockGasCost: inputStruct.MaxBlockGasCost, - BlockGasCostStep: inputStruct.BlockGasCostStep, + return nil, remainingGas, err } - return result, nil + return packedOutput, remainingGas, err } -// PackSetFeeConfig packs [inputStruct] of type SetFeeConfigInput into the appropriate arguments for setFeeConfig. -func PackSetFeeConfigV2(input commontype.FeeConfig) ([]byte, error) { - inputStruct := GetFeeConfigOutput{ - GasLimit: input.GasLimit, - TargetBlockRate: new(big.Int).SetUint64(input.TargetBlockRate), - MinBaseFee: input.MinBaseFee, - TargetGas: input.TargetGas, - BaseFeeChangeDenominator: input.BaseFeeChangeDenominator, - MinBlockGasCost: input.MinBlockGasCost, - MaxBlockGasCost: input.MaxBlockGasCost, - BlockGasCostStep: input.BlockGasCostStep, +// createFeeManagerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. +// Access to the getters/setters is controlled by an allow list for ContractAddress. +func createFeeManagerPrecompile() contract.StatefulPrecompiledContract { + var functions []*contract.StatefulPrecompileFunction + functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) + + abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ + "getFeeConfig": getFeeConfig, + "getFeeConfigLastChangedAt": getFeeConfigLastChangedAt, + "setFeeConfig": setFeeConfig, + } + + for name, function := range abiFunctionMap { + method, ok := FeeManagerABI.Methods[name] + if !ok { + panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) + } + functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) + } + // Construct the contract with no fallback function. + statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) + if err != nil { + panic(err) } - return IFeeManagerV2ABI.Pack("setFeeConfig", inputStruct.GasLimit, inputStruct.TargetBlockRate, inputStruct.MinBaseFee, inputStruct.TargetGas, inputStruct.BaseFeeChangeDenominator, inputStruct.MinBlockGasCost, inputStruct.MaxBlockGasCost, inputStruct.BlockGasCostStep) + return statefulContract } diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index 8cfeeec6a0..6a51951843 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -4,19 +4,17 @@ package feemanager import ( - "fmt" "math/big" - "math/rand" "testing" "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) @@ -119,6 +117,7 @@ var ( ExpectedRes: []byte{}, SetupBlockContext: func(mbc *contract.MockBlockContext) { mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes() + mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes() }, AfterHook: func(t testing.TB, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) @@ -136,11 +135,16 @@ var ( err := StoreFeeConfig(state, testFeeConfig, blockContext) require.NoError(t, err) }, - Input: PackGetFeeConfigInput(), + InputFn: func(t testing.TB) []byte { + input, err := PackGetFeeConfig() + require.NoError(t, err) + + return input + }, SuppliedGas: GetFeeConfigGasCost, ReadOnly: true, ExpectedRes: func() []byte { - res, err := PackFeeConfig(testFeeConfig) + res, err := PackGetFeeConfigOutput(testFeeConfig) if err != nil { panic(err) } @@ -154,16 +158,21 @@ var ( }, }, "get initial fee config": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - Input: PackGetFeeConfigInput(), + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackGetFeeConfig() + require.NoError(t, err) + + return input + }, SuppliedGas: GetFeeConfigGasCost, Config: &Config{ InitialFeeConfig: &testFeeConfig, }, ReadOnly: true, ExpectedRes: func() []byte { - res, err := PackFeeConfig(testFeeConfig) + res, err := PackGetFeeConfigOutput(testFeeConfig) if err != nil { panic(err) } @@ -188,10 +197,21 @@ var ( err := StoreFeeConfig(state, testFeeConfig, blockContext) require.NoError(t, err) }, - Input: PackGetLastChangedAtInput(), + InputFn: func(t testing.TB) []byte { + input, err := PackGetFeeConfigLastChangedAt() + require.NoError(t, err) + + return input + }, SuppliedGas: GetLastChangedAtGasCost, ReadOnly: true, - ExpectedRes: common.BigToHash(testBlockNumber).Bytes(), + ExpectedRes: func() []byte { + res, err := PackGetFeeConfigLastChangedAtOutput(testBlockNumber) + if err != nil { + panic(err) + } + return res + }(), AfterHook: func(t testing.TB, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) lastChangedAt := GetFeeConfigLastChangedAt(state) @@ -251,6 +271,58 @@ var ( ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, + "set config with extra padded bytes should fail before DUpgrade": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + + input = append(input, make([]byte, 32)...) + return input + }, + ChainConfigFn: func(t testing.TB) precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) + config.EXPECT().IsDUpgrade(gomock.Any()).Return(false).AnyTimes() + return config + }, + SuppliedGas: SetFeeConfigGasCost, + ReadOnly: false, + ExpectedErr: ErrInvalidLen.Error(), + SetupBlockContext: func(mbc *contract.MockBlockContext) { + mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes() + mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes() + }, + }, + "set config with extra padded bytes should succeed with DUpgrade": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + + input = append(input, make([]byte, 32)...) + return input + }, + ChainConfigFn: func(t testing.TB) precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) + config.EXPECT().IsDUpgrade(gomock.Any()).Return(true).AnyTimes() + return config + }, + SuppliedGas: SetFeeConfigGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + SetupBlockContext: func(mbc *contract.MockBlockContext) { + mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes() + mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes() + }, + AfterHook: func(t testing.TB, state contract.StateDB) { + feeConfig := GetStoredFeeConfig(state) + require.Equal(t, testFeeConfig, feeConfig) + lastChangedAt := GetFeeConfigLastChangedAt(state) + require.EqualValues(t, testBlockNumber, lastChangedAt) + }, + }, } ) @@ -261,273 +333,3 @@ func TestFeeManager(t *testing.T) { func BenchmarkFeeManager(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } -func TestGetFeeConfig(t *testing.T) { - // Compare PackGetFeeConfigV2 vs PackGetFeeConfig - // to see if they are equivalent - - input := PackGetFeeConfigInput() - - input2, err := PackGetFeeConfigV2() - require.NoError(t, err) - - require.Equal(t, input, input2) -} - -func TestGetFeeConfigOutput(t *testing.T) { - // Compare PackFeeConfigV2 vs PackFeeConfig - // to see if they are equivalent - - for i := 0; i < 1000; i++ { - feeConfig := commontype.FeeConfig{ - GasLimit: big.NewInt(rand.Int63()), - TargetBlockRate: rand.Uint64(), - - MinBaseFee: big.NewInt(rand.Int63()), - TargetGas: big.NewInt(rand.Int63()), - BaseFeeChangeDenominator: big.NewInt(rand.Int63()), - - MinBlockGasCost: big.NewInt(rand.Int63()), - MaxBlockGasCost: big.NewInt(rand.Int63()), - BlockGasCostStep: big.NewInt(rand.Int63()), - } - - testGetFeeConfigOutput(t, feeConfig) - } - // Some edge cases - testGetFeeConfigOutput(t, testFeeConfig) - // These should panic - require.Panics(t, func() { - _, _ = PackGetFeeConfigOutputV2(commontype.FeeConfig{}) - }) - require.Panics(t, func() { - _, _ = PackFeeConfig(commontype.FeeConfig{}) - }) - - // These should - unpacked, err := UnpackGetFeeConfigOutputV2([]byte{}) - require.Error(t, err) - - unpacked2, err := UnpackFeeConfigInput([]byte{}) - require.ErrorIs(t, err, ErrInvalidLen) - require.Equal(t, unpacked, unpacked2) - - // Test for extra padded bytes - input, err := PackGetFeeConfigOutputV2(testFeeConfig) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] - // add extra padded bytes - input = append(input, make([]byte, 32)...) - _, err = UnpackFeeConfigInput(input) - require.ErrorIs(t, err, ErrInvalidLen) - - _, err = UnpackGetFeeConfigOutputV2(input) - require.Error(t, err) -} - -func testGetFeeConfigOutput(t *testing.T, feeConfig commontype.FeeConfig) { - t.Helper() - t.Run(fmt.Sprintf("TestGetFeeConfigOutput, feeConfig %v", feeConfig), func(t *testing.T) { - // Test PackGetFeeConfigOutputV2, UnpackGetFeeConfigOutputV2 - input, err := PackGetFeeConfigOutputV2(feeConfig) - require.NoError(t, err) - - unpacked, err := UnpackGetFeeConfigOutputV2(input) - require.NoError(t, err) - - require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) - - // Test PackGetFeeConfigOutput, UnpackGetFeeConfigOutput - input, err = PackFeeConfig(feeConfig) - require.NoError(t, err) - - unpacked, err = UnpackFeeConfigInput(input) - require.NoError(t, err) - - require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) - - // // now mix and match - // Test PackGetFeeConfigOutput, PackGetFeeConfigOutputV2 - input, err = PackGetFeeConfigOutputV2(feeConfig) - require.NoError(t, err) - input2, err := PackFeeConfig(feeConfig) - require.NoError(t, err) - require.Equal(t, input, input2) - - // // Test UnpackGetFeeConfigOutput, UnpackGetFeeConfigOutputV2 - unpacked, err = UnpackGetFeeConfigOutputV2(input2) - require.NoError(t, err) - unpacked2, err := UnpackFeeConfigInput(input) - require.NoError(t, err) - require.True(t, unpacked.Equal(&unpacked2), "not equal: unpacked %v, unpacked2 %v", unpacked, unpacked2) - }) -} - -func TestGetLastChangedAtInput(t *testing.T) { - // Compare PackGetFeeConfigLastChangedAtV2 vs PackGetLastChangedAtInput - // to see if they are equivalent - - input := PackGetLastChangedAtInput() - - input2, err := PackGetFeeConfigLastChangedAtV2() - require.NoError(t, err) - - require.Equal(t, input, input2) -} - -func TestGetLastChangedAtOutput(t *testing.T) { - // Compare PackGetFeeConfigLastChangedAtOutputV2 vs PackGetLastChangedAtOutput - // to see if they are equivalent - - for i := 0; i < 1000; i++ { - lastChangedAt := big.NewInt(rand.Int63()) - testGetLastChangedAtOutput(t, lastChangedAt) - } - // Some edge cases - testGetLastChangedAtOutput(t, big.NewInt(0)) - testGetLastChangedAtOutput(t, big.NewInt(1)) - testGetLastChangedAtOutput(t, big.NewInt(2)) - testGetLastChangedAtOutput(t, math.MaxBig256) - testGetLastChangedAtOutput(t, math.MaxBig256.Sub(math.MaxBig256, common.Big1)) - testGetLastChangedAtOutput(t, math.MaxBig256.Add(math.MaxBig256, common.Big1)) -} - -func testGetLastChangedAtOutput(t *testing.T, lastChangedAt *big.Int) { - t.Helper() - t.Run(fmt.Sprintf("TestGetLastChangedAtOutput, lastChangedAt %v", lastChangedAt), func(t *testing.T) { - // Test PackGetFeeConfigLastChangedAtOutputV2, UnpackGetFeeConfigLastChangedAtOutputV2 - input, err := PackGetFeeConfigLastChangedAtOutputV2(lastChangedAt) - require.NoError(t, err) - - unpacked, err := UnpackGetFeeConfigLastChangedAtOutputV2(input) - require.NoError(t, err) - - require.Zero(t, lastChangedAt.Cmp(unpacked), "not equal: lastChangedAt %v, unpacked %v", lastChangedAt, unpacked) - - // Test PackGetLastChangedAtOutput, UnpackGetLastChangedAtOutput - input = common.BigToHash(lastChangedAt).Bytes() - - unpacked = common.BytesToHash(input).Big() - require.NoError(t, err) - - require.Zero(t, lastChangedAt.Cmp(unpacked), "not equal: lastChangedAt %v, unpacked %v", lastChangedAt) - - // now mix and match - // Test PackGetLastChangedAtOutput, PackGetFeeConfigLastChangedAtOutputV2 - input = common.BigToHash(lastChangedAt).Bytes() - require.NoError(t, err) - input2, err := PackGetFeeConfigLastChangedAtOutputV2(lastChangedAt) - require.NoError(t, err) - require.Equal(t, input, input2) - - // Test UnpackGetLastChangedAtOutput, UnpackGetFeeConfigLastChangedAtOutputV2 - unpacked = common.BytesToHash(input).Big() - require.NoError(t, err) - unpacked2, err := UnpackGetFeeConfigLastChangedAtOutputV2(input) - require.NoError(t, err) - require.EqualValues(t, unpacked, unpacked2) - }) -} - -func TestPackSetFeeConfigInput(t *testing.T) { - // Compare PackSetFeeConfigV2 vs PackSetFeeConfig - // to see if they are equivalent - for i := 0; i < 1000; i++ { - feeConfig := commontype.FeeConfig{ - GasLimit: big.NewInt(rand.Int63()), - TargetBlockRate: rand.Uint64(), - - MinBaseFee: big.NewInt(rand.Int63()), - TargetGas: big.NewInt(rand.Int63()), - BaseFeeChangeDenominator: big.NewInt(rand.Int63()), - - MinBlockGasCost: big.NewInt(rand.Int63()), - MaxBlockGasCost: big.NewInt(rand.Int63()), - BlockGasCostStep: big.NewInt(rand.Int63()), - } - - testPackSetFeeConfigInput(t, feeConfig) - } - // Some edge cases - // Some edge cases - testPackSetFeeConfigInput(t, testFeeConfig) - // These should panic - require.Panics(t, func() { - _, _ = PackSetFeeConfigV2(commontype.FeeConfig{}) - }) - require.Panics(t, func() { - _, _ = PackSetFeeConfig(commontype.FeeConfig{}) - }) - - // These should err - _, err := UnpackSetFeeConfigInputV2([]byte{123}, true) - require.ErrorIs(t, err, ErrInvalidLen) - - _, err = UnpackSetFeeConfigInputV2([]byte{123}, false) - require.ErrorContains(t, err, "abi: improperly formatted input") - - _, err = UnpackFeeConfigInput([]byte{123}) - require.ErrorIs(t, err, ErrInvalidLen) - - // Test for extra padded bytes - input, err := PackSetFeeConfigV2(testFeeConfig) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] - // add extra padded bytes - input = append(input, make([]byte, 32)...) - _, err = UnpackFeeConfigInput(input) - require.ErrorIs(t, err, ErrInvalidLen) - - _, err = UnpackSetFeeConfigInputV2(input, true) - require.ErrorIs(t, err, ErrInvalidLen) - - unpacked, err := UnpackSetFeeConfigInputV2(input, false) - require.NoError(t, err) - require.True(t, testFeeConfig.Equal(&unpacked)) -} - -func testPackSetFeeConfigInput(t *testing.T, feeConfig commontype.FeeConfig) { - t.Helper() - t.Run(fmt.Sprintf("TestPackSetFeeConfigInput, feeConfig %v", feeConfig), func(t *testing.T) { - // Test PackSetFeeConfigV2, UnpackSetFeeConfigInputV2 - input, err := PackSetFeeConfigV2(feeConfig) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] - - unpacked, err := UnpackSetFeeConfigInputV2(input, true) - require.NoError(t, err) - - require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) - - // Test PackSetFeeConfig, UnpackFeeConfigInput - input, err = PackSetFeeConfig(feeConfig) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] - - unpacked, err = UnpackFeeConfigInput(input) - require.NoError(t, err) - - require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig) - - // now mix and match - // Test PackSetFeeConfig, PackSetFeeConfigV2 - input, err = PackSetFeeConfig(feeConfig) - require.NoError(t, err) - input2, err := PackSetFeeConfigV2(feeConfig) - require.NoError(t, err) - require.Equal(t, input, input2) - // exclude 4 bytes for function selector - input = input[4:] - input2 = input2[4:] - - // Test UnpackSetFeeConfigInputV2, UnpackFeeConfigInput - unpacked, err = UnpackSetFeeConfigInputV2(input2, true) - require.NoError(t, err) - unpacked2, err := UnpackFeeConfigInput(input) - require.NoError(t, err) - require.EqualValues(t, unpacked, unpacked2) - }) -} diff --git a/precompile/contracts/feemanager/module.go b/precompile/contracts/feemanager/module.go index ab26e22f9d..8bf4638922 100644 --- a/precompile/contracts/feemanager/module.go +++ b/precompile/contracts/feemanager/module.go @@ -20,6 +20,7 @@ const ConfigKey = "feeManagerConfig" var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000003") +// Module is the precompile module. It is used to register the precompile contract. var Module = modules.Module{ ConfigKey: ConfigKey, Address: ContractAddress, @@ -30,16 +31,21 @@ var Module = modules.Module{ type configurator struct{} func init() { + // Register the precompile module. + // Each precompile contract registers itself through [RegisterModule] function. if err := modules.RegisterModule(Module); err != nil { panic(err) } } +// MakeConfig returns a new precompile config instance. +// This is required for Marshal/Unmarshal the precompile config. func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) } -// Configure configures [state] with the desired admins based on [configIface]. +// Configure configures [state] with the given [cfg] precompileconfig. +// This function is called by the EVM once per precompile contract activation. func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { config, ok := cfg.(*Config) if !ok { diff --git a/precompile/contracts/nativeminter/contract.go b/precompile/contracts/nativeminter/contract.go index 7ecde8c507..998c5945b9 100644 --- a/precompile/contracts/nativeminter/contract.go +++ b/precompile/contracts/nativeminter/contract.go @@ -33,7 +33,7 @@ var ( ErrCannotMint = errors.New("non-enabled cannot mint") ErrInvalidLen = errors.New("invalid input length for minting") - // NativeMinterRawABI contains the raw ABI of NativeMinterV2 contract. + // NativeMinterRawABI contains the raw ABI of NativeMinter contract. //go:embed contract.abi NativeMinterRawABI string @@ -51,15 +51,20 @@ func SetContractNativeMinterStatus(stateDB contract.StateDB, address common.Addr allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) } +// PackMintNativeCoin packs [address] and [amount] into the appropriate arguments for mintNativeCoin. +func PackMintNativeCoin(address common.Address, amount *big.Int) ([]byte, error) { + return NativeMinterABI.Pack("mintNativeCoin", address, amount) +} + // UnpackMintNativeCoinInput attempts to unpack [input] as address and amount. // assumes that [input] does not include selector (omits first 4 func signature bytes) -// if [doLenCheck] is true, it will return an error if the length of [input] is not [mintInputLen] -func UnpackMintNativeCoinInput(input []byte, doLenCheck bool) (common.Address, *big.Int, error) { +// if [skipLenCheck] is false, it will return an error if the length of [input] is not [mintInputLen] +func UnpackMintNativeCoinInput(input []byte, skipLenCheck bool) (common.Address, *big.Int, error) { // Initially we had this check to ensure that the input was the correct length. // However solidity does not always pack the input to the correct length, and allows // for extra padding bytes to be added to the end of the input. Therefore, we have removed // this check with the DUpgrade. We still need to keep this check for backwards compatibility. - if doLenCheck && len(input) != mintInputLen { + if !skipLenCheck && len(input) != mintInputLen { return common.Address{}, nil, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) } inputStruct := MintNativeCoinInput{} @@ -68,11 +73,6 @@ func UnpackMintNativeCoinInput(input []byte, doLenCheck bool) (common.Address, * return inputStruct.Addr, inputStruct.Amount, err } -// PackMintNativeCoin packs [address] and [amount] into the appropriate arguments for mintNativeCoin. -func PackMintNativeCoin(address common.Address, amount *big.Int) ([]byte, error) { - return NativeMinterABI.Pack("mintNativeCoin", address, amount) -} - // mintNativeCoin checks if the caller is permissioned for minting operation. // The execution function parses the [input] into native coin amount and receiver address. func mintNativeCoin(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { @@ -83,8 +83,9 @@ func mintNativeCoin(accessibleState contract.AccessibleState, caller common.Addr if readOnly { return nil, remainingGas, vmerrs.ErrWriteProtection } - doCheckLen := contract.IsDUpgradeActivated(accessibleState) - to, amount, err := UnpackMintNativeCoinInput(input, doCheckLen) + + skipLenCheck := contract.IsDUpgradeActivated(accessibleState) + to, amount, err := UnpackMintNativeCoinInput(input, skipLenCheck) if err != nil { return nil, remainingGas, err } diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index 044cdead02..32e83cda5a 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -9,11 +9,13 @@ import ( "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" ) var tests = map[string]testutils.PrecompileTest{ @@ -158,6 +160,51 @@ var tests = map[string]testutils.PrecompileTest{ ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, + "mint with extra padded bytes should fail before DUpgrade": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + ChainConfigFn: func(t testing.TB) precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) + config.EXPECT().IsDUpgrade(gomock.Any()).Return(false).AnyTimes() + return config + }, + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) + + // Add extra bytes to the end of the input + input = append(input, make([]byte, 32)...) + + return input + }, + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedErr: ErrInvalidLen.Error(), + }, + "mint with extra padded bytes should succeed with DUpgrade": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + ChainConfigFn: func(t testing.TB) precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) + config.EXPECT().IsDUpgrade(gomock.Any()).Return(true).AnyTimes() + return config + }, + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) + + // Add extra bytes to the end of the input + input = append(input, make([]byte, 32)...) + + return input + }, + ExpectedRes: []byte{}, + SuppliedGas: MintGasCost, + ReadOnly: false, + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") + }, + }, } func TestContractNativeMinterRun(t *testing.T) { diff --git a/precompile/testutils/test_precompile.go b/precompile/testutils/test_precompile.go index c0c0a797c2..7a9b6c7358 100644 --- a/precompile/testutils/test_precompile.go +++ b/precompile/testutils/test_precompile.go @@ -50,6 +50,9 @@ type PrecompileTest struct { // ChainConfig is the chain config to use for the precompile's block context // If nil, the default chain config will be used. ChainConfig precompileconfig.ChainConfig + // ChainConfigFn is a function that returns the chain config to use for the precompile's block context + // If specified, ChainConfig will be ignored. + ChainConfigFn func(t testing.TB) precompileconfig.ChainConfig } type PrecompileRunparams struct { @@ -91,6 +94,9 @@ func (test PrecompileTest) setup(t testing.TB, module modules.Module, state cont } chainConfig := test.ChainConfig + if test.ChainConfigFn != nil { + chainConfig = test.ChainConfigFn(t) + } if chainConfig == nil { mockChainConfig := precompileconfig.NewMockChainConfig(ctrl) mockChainConfig.EXPECT().GetFeeConfig().AnyTimes().Return(commontype.ValidTestFeeConfig) From 0585f8932b3565880b8f032c4946cf54fe16d152 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 14 Sep 2023 17:17:34 +0300 Subject: [PATCH 08/19] remove generated abis --- contracts/abis/IAllowList.abi | 1 - contracts/abis/IFeeManager.abi | 1 - contracts/abis/INativeMinter.abi | 1 - 3 files changed, 3 deletions(-) delete mode 100644 contracts/abis/IAllowList.abi delete mode 100644 contracts/abis/IFeeManager.abi delete mode 100644 contracts/abis/INativeMinter.abi diff --git a/contracts/abis/IAllowList.abi b/contracts/abis/IAllowList.abi deleted file mode 100644 index 1e11d4b265..0000000000 --- a/contracts/abis/IAllowList.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contracts/abis/IFeeManager.abi b/contracts/abis/IFeeManager.abi deleted file mode 100644 index 0e4d5f28d7..0000000000 --- a/contracts/abis/IFeeManager.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[],"name":"getFeeConfig","outputs":[{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"targetBlockRate","type":"uint256"},{"internalType":"uint256","name":"minBaseFee","type":"uint256"},{"internalType":"uint256","name":"targetGas","type":"uint256"},{"internalType":"uint256","name":"baseFeeChangeDenominator","type":"uint256"},{"internalType":"uint256","name":"minBlockGasCost","type":"uint256"},{"internalType":"uint256","name":"maxBlockGasCost","type":"uint256"},{"internalType":"uint256","name":"blockGasCostStep","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFeeConfigLastChangedAt","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"targetBlockRate","type":"uint256"},{"internalType":"uint256","name":"minBaseFee","type":"uint256"},{"internalType":"uint256","name":"targetGas","type":"uint256"},{"internalType":"uint256","name":"baseFeeChangeDenominator","type":"uint256"},{"internalType":"uint256","name":"minBlockGasCost","type":"uint256"},{"internalType":"uint256","name":"maxBlockGasCost","type":"uint256"},{"internalType":"uint256","name":"blockGasCostStep","type":"uint256"}],"name":"setFeeConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contracts/abis/INativeMinter.abi b/contracts/abis/INativeMinter.abi deleted file mode 100644 index ba534bbf66..0000000000 --- a/contracts/abis/INativeMinter.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mintNativeCoin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file From 97357bf066e2b0d34ac695952b395755ae221e69 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 14 Sep 2023 21:21:42 +0300 Subject: [PATCH 09/19] Update precompile/contracts/feemanager/contract.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur --- precompile/contracts/feemanager/contract.go | 1 - 1 file changed, 1 deletion(-) diff --git a/precompile/contracts/feemanager/contract.go b/precompile/contracts/feemanager/contract.go index 5f5df58b17..7445361d9a 100644 --- a/precompile/contracts/feemanager/contract.go +++ b/precompile/contracts/feemanager/contract.go @@ -184,7 +184,6 @@ func UnpackSetFeeConfigInput(input []byte, skipLenCheck bool) (commontype.FeeCon } inputStruct := FeeConfigABIStruct{} err := FeeManagerABI.UnpackInputIntoInterface(&inputStruct, "setFeeConfig", input) - if err != nil { return commontype.FeeConfig{}, err } From f0c504cc0221c584fb659caef1d5587b03f60acb Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 14 Sep 2023 21:25:44 +0300 Subject: [PATCH 10/19] fix comments --- .../abi/bind/precompilebind/precompile_module_template.go | 2 +- precompile/contracts/deployerallowlist/module.go | 5 ++++- precompile/contracts/nativeminter/module.go | 7 +++++-- precompile/contracts/txallowlist/module.go | 5 ++++- x/warp/module.go | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/accounts/abi/bind/precompilebind/precompile_module_template.go b/accounts/abi/bind/precompilebind/precompile_module_template.go index 2af917b7d9..fd9d45908d 100644 --- a/accounts/abi/bind/precompilebind/precompile_module_template.go +++ b/accounts/abi/bind/precompilebind/precompile_module_template.go @@ -22,7 +22,7 @@ import ( var _ contract.Configurator = &configurator{} -// ConfigKey is the key used in json config files to specify this precompile precompileconfig. +// ConfigKey is the key used in json config files to specify this precompile config. // must be unique across all precompiles. const ConfigKey = "{{decapitalise .Contract.Type}}Config" diff --git a/precompile/contracts/deployerallowlist/module.go b/precompile/contracts/deployerallowlist/module.go index 210d1c4e38..4784111ac4 100644 --- a/precompile/contracts/deployerallowlist/module.go +++ b/precompile/contracts/deployerallowlist/module.go @@ -35,11 +35,14 @@ func init() { } } +// MakeConfig returns a new precompile config instance. +// This is required for Marshal/Unmarshal the precompile config. func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) } -// Configure configures [state] with the given [cfg] config. +// Configure configures [state] with the given [cfg] precompileconfig. +// This function is called by the EVM once per precompile contract activation. func (c *configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { config, ok := cfg.(*Config) if !ok { diff --git a/precompile/contracts/nativeminter/module.go b/precompile/contracts/nativeminter/module.go index 0fd352f607..31571a1503 100644 --- a/precompile/contracts/nativeminter/module.go +++ b/precompile/contracts/nativeminter/module.go @@ -15,7 +15,7 @@ import ( var _ contract.Configurator = &configurator{} -// ConfigKey is the key used in json config files to specify this precompile precompileconfig. +// ConfigKey is the key used in json config files to specify this precompile config. // must be unique across all precompiles. const ConfigKey = "contractNativeMinterConfig" @@ -37,11 +37,14 @@ func init() { } } +// MakeConfig returns a new precompile config instance. +// This is required for Marshal/Unmarshal the precompile config. func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) } -// Configure configures [state] with the desired admins based on [cfg]. +// Configure configures [state] with the given [cfg] precompileconfig. +// This function is called by the EVM once per precompile contract activation. func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { config, ok := cfg.(*Config) if !ok { diff --git a/precompile/contracts/txallowlist/module.go b/precompile/contracts/txallowlist/module.go index 0c71e19c3c..851c7212de 100644 --- a/precompile/contracts/txallowlist/module.go +++ b/precompile/contracts/txallowlist/module.go @@ -35,11 +35,14 @@ func init() { } } +// MakeConfig returns a new precompile config instance. +// This is required for Marshal/Unmarshal the precompile config. func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) } -// Configure configures [state] with the initial state for the precompile. +// Configure configures [state] with the given [cfg] precompileconfig. +// This function is called by the EVM once per precompile contract activation. func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { config, ok := cfg.(*Config) if !ok { diff --git a/x/warp/module.go b/x/warp/module.go index 84e15f578a..9a8488a222 100644 --- a/x/warp/module.go +++ b/x/warp/module.go @@ -15,7 +15,7 @@ import ( var _ contract.Configurator = &configurator{} -// ConfigKey is the key used in json config files to specify this precompile precompileconfig. +// ConfigKey is the key used in json config files to specify this precompile config. // must be unique across all precompiles. const ConfigKey = "warpConfig" From d953bf1dd6d73460716461e9fbb874dbc3d8459b Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 2 Nov 2023 15:54:04 +0300 Subject: [PATCH 11/19] readd pack/unpack tests --- .../contracts/feemanager/contract_test.go | 139 ++++++++++++++++++ .../contracts/nativeminter/contract_test.go | 55 +++++++ 2 files changed, 194 insertions(+) diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index 6a51951843..c719d3b29e 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -4,7 +4,9 @@ package feemanager import ( + "fmt" "math/big" + "math/rand" "testing" "github.com/ava-labs/subnet-evm/commontype" @@ -15,6 +17,7 @@ import ( "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) @@ -333,3 +336,139 @@ func TestFeeManager(t *testing.T) { func BenchmarkFeeManager(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } + +func TestPackUnpackGetFeeConfigOutput(t *testing.T) { + for i := 0; i < 1000; i++ { + feeConfig := commontype.FeeConfig{ + GasLimit: big.NewInt(rand.Int63()), + TargetBlockRate: rand.Uint64(), + + MinBaseFee: big.NewInt(rand.Int63()), + TargetGas: big.NewInt(rand.Int63()), + BaseFeeChangeDenominator: big.NewInt(rand.Int63()), + + MinBlockGasCost: big.NewInt(rand.Int63()), + MaxBlockGasCost: big.NewInt(rand.Int63()), + BlockGasCostStep: big.NewInt(rand.Int63()), + } + + testGetFeeConfigOutput(t, feeConfig) + } + // Some edge cases + testGetFeeConfigOutput(t, testFeeConfig) + // should panic + require.Panics(t, func() { + _, _ = PackGetFeeConfigOutput(commontype.FeeConfig{}) + }) + + _, err := UnpackGetFeeConfigOutput([]byte{}) + require.Error(t, err) +} + +func testGetFeeConfigOutput(t *testing.T, feeConfig commontype.FeeConfig) { + t.Helper() + t.Run(fmt.Sprintf("TestGetFeeConfigOutput, feeConfig %v", feeConfig), func(t *testing.T) { + input, err := PackGetFeeConfigOutput(feeConfig) + require.NoError(t, err) + + unpacked, err := UnpackGetFeeConfigOutput(input) + require.NoError(t, err) + + require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + }) +} + +func TestGetLastChangedAtOutput(t *testing.T) { + // Compare PackGetFeeConfigLastChangedAtOutputV2 vs PackGetLastChangedAtOutput + // to see if they are equivalent + + for i := 0; i < 1000; i++ { + lastChangedAt := big.NewInt(rand.Int63()) + testGetLastChangedAtOutput(t, lastChangedAt) + } + // Some edge cases + testGetLastChangedAtOutput(t, big.NewInt(0)) + testGetLastChangedAtOutput(t, big.NewInt(1)) + testGetLastChangedAtOutput(t, big.NewInt(2)) + testGetLastChangedAtOutput(t, math.MaxBig256) + testGetLastChangedAtOutput(t, math.MaxBig256.Sub(math.MaxBig256, common.Big1)) + testGetLastChangedAtOutput(t, math.MaxBig256.Add(math.MaxBig256, common.Big1)) +} + +func testGetLastChangedAtOutput(t *testing.T, lastChangedAt *big.Int) { + t.Helper() + t.Run(fmt.Sprintf("TestGetLastChangedAtOutput, lastChangedAt %v", lastChangedAt), func(t *testing.T) { + // Test PackGetFeeConfigLastChangedAtOutputV2, UnpackGetFeeConfigLastChangedAtOutputV2 + input, err := PackGetFeeConfigLastChangedAtOutput(lastChangedAt) + require.NoError(t, err) + + unpacked, err := UnpackGetFeeConfigLastChangedAtOutput(input) + require.NoError(t, err) + + require.Zero(t, lastChangedAt.Cmp(unpacked), "not equal: lastChangedAt %v, unpacked %v", lastChangedAt, unpacked) + }) +} + +func TestPackSetFeeConfigInput(t *testing.T) { + // Compare PackSetFeeConfigV2 vs PackSetFeeConfig + // to see if they are equivalent + for i := 0; i < 1000; i++ { + feeConfig := commontype.FeeConfig{ + GasLimit: big.NewInt(rand.Int63()), + TargetBlockRate: rand.Uint64(), + + MinBaseFee: big.NewInt(rand.Int63()), + TargetGas: big.NewInt(rand.Int63()), + BaseFeeChangeDenominator: big.NewInt(rand.Int63()), + + MinBlockGasCost: big.NewInt(rand.Int63()), + MaxBlockGasCost: big.NewInt(rand.Int63()), + BlockGasCostStep: big.NewInt(rand.Int63()), + } + + testPackSetFeeConfigInput(t, feeConfig) + } + // Some edge cases + // Some edge cases + testPackSetFeeConfigInput(t, testFeeConfig) + // These should panic + require.Panics(t, func() { + _, _ = PackSetFeeConfig(commontype.FeeConfig{}) + }) + + // These should err + _, err := UnpackSetFeeConfigInput([]byte{123}, false) + require.ErrorIs(t, err, ErrInvalidLen) + + _, err = UnpackSetFeeConfigInput([]byte{123}, true) + require.ErrorContains(t, err, "abi: improperly formatted input") + + // Test for extra padded bytes + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + // add extra padded bytes + input = append(input, make([]byte, 32)...) + _, err = UnpackSetFeeConfigInput(input, false) + require.ErrorIs(t, err, ErrInvalidLen) + + unpacked, err := UnpackSetFeeConfigInput(input, true) + require.NoError(t, err) + require.True(t, testFeeConfig.Equal(&unpacked)) +} + +func testPackSetFeeConfigInput(t *testing.T, feeConfig commontype.FeeConfig) { + t.Helper() + t.Run(fmt.Sprintf("TestPackSetFeeConfigInput, feeConfig %v", feeConfig), func(t *testing.T) { + input, err := PackSetFeeConfig(feeConfig) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + + unpacked, err := UnpackSetFeeConfigInput(input, true) + require.NoError(t, err) + + require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + }) +} diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index 32e83cda5a..71fd53ce39 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -4,8 +4,12 @@ package nativeminter import ( + "fmt" + "math/big" + "math/rand" "testing" + "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contract" @@ -14,6 +18,7 @@ import ( "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) @@ -214,3 +219,53 @@ func TestContractNativeMinterRun(t *testing.T) { func BenchmarkContractNativeMinter(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } + +func TestPackUnpackMintNativeCoinInput(t *testing.T) { + // Test PackMintNativeCoin, UnpackMintNativeCoinInput + // for 1000 random addresses and amounts + for i := 0; i < 1000; i++ { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + amount := new(big.Int).SetInt64(rand.Int63()) + testUnpackAndPacks(t, addr, amount) + } + + // Some edge cases + testUnpackAndPacks(t, common.Address{}, common.Big0) + testUnpackAndPacks(t, common.Address{}, common.Big1) + testUnpackAndPacks(t, common.Address{}, math.MaxBig256) + testUnpackAndPacks(t, common.Address{}, math.MaxBig256.Sub(math.MaxBig256, common.Big1)) + testUnpackAndPacks(t, common.Address{}, math.MaxBig256.Add(math.MaxBig256, common.Big1)) + testUnpackAndPacks(t, constants.BlackholeAddr, common.Big2) + + input, err := PackMintNativeCoin(constants.BlackholeAddr, common.Big2) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + // add extra padded bytes + input = append(input, make([]byte, 32)...) + + _, _, err = UnpackMintNativeCoinInput(input, false) + require.ErrorIs(t, err, ErrInvalidLen) + + addr, amount, err := UnpackMintNativeCoinInput(input, true) + require.NoError(t, err) + require.Equal(t, constants.BlackholeAddr, addr) + require.Equal(t, common.Big2.Bytes(), amount.Bytes()) +} + +func testUnpackAndPacks(t *testing.T, addr common.Address, amount *big.Int) { + t.Helper() + t.Run(fmt.Sprintf("TestUnpackAndPacks, addr: %s, amount: %s", addr.String(), amount.String()), func(t *testing.T) { + input, err := PackMintNativeCoin(addr, amount) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + + unpackedAddr, unpackedAmount, err := UnpackMintNativeCoinInput(input, true) + require.NoError(t, err) + + require.EqualValues(t, addr, unpackedAddr) + require.Equal(t, amount.Bytes(), unpackedAmount.Bytes()) + }) +} From 467eac4f9ed79d6907c94962c1efe5b4280fdb57 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 28 Nov 2023 20:10:59 +0300 Subject: [PATCH 12/19] add signature tests --- precompile/contract/contract.go | 1 - precompile/contracts/feemanager/contract_test.go | 14 ++++++++++++++ precompile/contracts/nativeminter/contract_test.go | 7 +++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/precompile/contract/contract.go b/precompile/contract/contract.go index f843e2a15b..0be76a8955 100644 --- a/precompile/contract/contract.go +++ b/precompile/contract/contract.go @@ -22,7 +22,6 @@ type ActivationFunc func(AccessibleState) bool // StatefulPrecompileFunction defines a function implemented by a stateful precompile type StatefulPrecompileFunction struct { // selector is the 4 byte function selector for this function - // This should be calculated from the function signature using CalculateFunctionSelector selector []byte // execute is performed when this function is selected execute RunStatefulPrecompileFunc diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index c719d3b29e..cbca538d89 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -458,6 +458,20 @@ func TestPackSetFeeConfigInput(t *testing.T) { require.True(t, testFeeConfig.Equal(&unpacked)) } +func TestSignatures(t *testing.T) { + setFeeConfigSignature := contract.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") + abiSetFeeConfig := FeeManagerABI.Methods["setFeeConfig"] + require.Equal(t, setFeeConfigSignature, abiSetFeeConfig.ID) + + getFeeConfigSignature := contract.CalculateFunctionSelector("getFeeConfig()") + abiGetFeeConfig := FeeManagerABI.Methods["getFeeConfig"] + require.Equal(t, getFeeConfigSignature, abiGetFeeConfig.ID) + + getFeeConfigLastChangedAtSignature := contract.CalculateFunctionSelector("getFeeConfigLastChangedAt()") + abiGetFeeConfigLastChangedAt := FeeManagerABI.Methods["getFeeConfigLastChangedAt"] + require.Equal(t, getFeeConfigLastChangedAtSignature, abiGetFeeConfigLastChangedAt.ID) +} + func testPackSetFeeConfigInput(t *testing.T, feeConfig commontype.FeeConfig) { t.Helper() t.Run(fmt.Sprintf("TestPackSetFeeConfigInput, feeConfig %v", feeConfig), func(t *testing.T) { diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index 71fd53ce39..477fc24396 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -254,6 +254,13 @@ func TestPackUnpackMintNativeCoinInput(t *testing.T) { require.Equal(t, common.Big2.Bytes(), amount.Bytes()) } +func TestSignatures(t *testing.T) { + // Test that the mintNativeCoin signature is correct + mintSignature := contract.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount + abiMintNativeCoin := NativeMinterABI.Methods["mintNativeCoin"] + require.Equal(t, mintSignature, abiMintNativeCoin.ID) +} + func testUnpackAndPacks(t *testing.T, addr common.Address, amount *big.Int) { t.Helper() t.Run(fmt.Sprintf("TestUnpackAndPacks, addr: %s, amount: %s", addr.String(), amount.String()), func(t *testing.T) { From f8040661d2e6f4a9c82997b3ba4a618c62ded3bd Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 6 Dec 2023 13:40:26 +0300 Subject: [PATCH 13/19] remove skip len check vars --- precompile/contracts/feemanager/contract.go | 4 ++-- precompile/contracts/nativeminter/contract.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/precompile/contracts/feemanager/contract.go b/precompile/contracts/feemanager/contract.go index 7445361d9a..03cc91ec76 100644 --- a/precompile/contracts/feemanager/contract.go +++ b/precompile/contracts/feemanager/contract.go @@ -213,8 +213,8 @@ func setFeeConfig(accessibleState contract.AccessibleState, caller common.Addres return nil, remainingGas, vmerrs.ErrWriteProtection } - skipLenCheck := contract.IsDUpgradeActivated(accessibleState) - feeConfig, err := UnpackSetFeeConfigInput(input, skipLenCheck) + // We skip the fixed length check with DUpgrade + feeConfig, err := UnpackSetFeeConfigInput(input, contract.IsDUpgradeActivated(accessibleState)) if err != nil { return nil, remainingGas, err } diff --git a/precompile/contracts/nativeminter/contract.go b/precompile/contracts/nativeminter/contract.go index 998c5945b9..4a77985131 100644 --- a/precompile/contracts/nativeminter/contract.go +++ b/precompile/contracts/nativeminter/contract.go @@ -84,8 +84,8 @@ func mintNativeCoin(accessibleState contract.AccessibleState, caller common.Addr return nil, remainingGas, vmerrs.ErrWriteProtection } - skipLenCheck := contract.IsDUpgradeActivated(accessibleState) - to, amount, err := UnpackMintNativeCoinInput(input, skipLenCheck) + // We skip the fixed length check with DUpgrade + to, amount, err := UnpackMintNativeCoinInput(input, contract.IsDUpgradeActivated(accessibleState)) if err != nil { return nil, remainingGas, err } From 8f69a1887378bbf421724f5047a632c55fba009f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 6 Dec 2023 15:48:08 +0300 Subject: [PATCH 14/19] add native minter equality tests --- precompile/contract/test_utils.go | 49 ++ .../contracts/feemanager/contract_test.go | 9 +- .../contracts/nativeminter/contract_test.go | 441 ++++++++++-------- 3 files changed, 295 insertions(+), 204 deletions(-) create mode 100644 precompile/contract/test_utils.go diff --git a/precompile/contract/test_utils.go b/precompile/contract/test_utils.go new file mode 100644 index 0000000000..cf38b5db77 --- /dev/null +++ b/precompile/contract/test_utils.go @@ -0,0 +1,49 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package contract + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +// PackOrderedHashesWithSelector packs the function selector and ordered list of hashes into [dst] +// byte slice. +// assumes that [dst] has sufficient room for [functionSelector] and [hashes]. +// Kept for testing backwards compatibility. +func PackOrderedHashesWithSelector(dst []byte, functionSelector []byte, hashes []common.Hash) error { + copy(dst[:len(functionSelector)], functionSelector) + return PackOrderedHashes(dst[len(functionSelector):], hashes) +} + +// PackOrderedHashes packs the ordered list of [hashes] into the [dst] byte buffer. +// assumes that [dst] has sufficient space to pack [hashes] or else this function will panic. +// Kept for testing backwards compatibility. +func PackOrderedHashes(dst []byte, hashes []common.Hash) error { + if len(dst) != len(hashes)*common.HashLength { + return fmt.Errorf("destination byte buffer has insufficient length (%d) for %d hashes", len(dst), len(hashes)) + } + + var ( + start = 0 + end = common.HashLength + ) + for _, hash := range hashes { + copy(dst[start:end], hash.Bytes()) + start += common.HashLength + end += common.HashLength + } + return nil +} + +// PackedHash returns packed the byte slice with common.HashLength from [packed] +// at the given [index]. +// Assumes that [packed] is composed entirely of packed 32 byte segments. +// Kept for testing backwards compatibility. +func PackedHash(packed []byte, index int) []byte { + start := common.HashLength * index + end := start + common.HashLength + return packed[start:end] +} diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index cbca538d89..cee31db797 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -23,6 +23,10 @@ import ( ) var ( + setFeeConfigSignature = contract.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") + getFeeConfigSignature = contract.CalculateFunctionSelector("getFeeConfig()") + getFeeConfigLastChangedAtSignature = contract.CalculateFunctionSelector("getFeeConfigLastChangedAt()") + testFeeConfig = commontype.FeeConfig{ GasLimit: big.NewInt(8_000_000), TargetBlockRate: 2, // in seconds @@ -458,16 +462,13 @@ func TestPackSetFeeConfigInput(t *testing.T) { require.True(t, testFeeConfig.Equal(&unpacked)) } -func TestSignatures(t *testing.T) { - setFeeConfigSignature := contract.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") +func TestFunctionSignatures(t *testing.T) { abiSetFeeConfig := FeeManagerABI.Methods["setFeeConfig"] require.Equal(t, setFeeConfigSignature, abiSetFeeConfig.ID) - getFeeConfigSignature := contract.CalculateFunctionSelector("getFeeConfig()") abiGetFeeConfig := FeeManagerABI.Methods["getFeeConfig"] require.Equal(t, getFeeConfigSignature, abiGetFeeConfig.ID) - getFeeConfigLastChangedAtSignature := contract.CalculateFunctionSelector("getFeeConfigLastChangedAt()") abiGetFeeConfigLastChangedAt := FeeManagerABI.Methods["getFeeConfigLastChangedAt"] require.Equal(t, getFeeConfigLastChangedAtSignature, abiGetFeeConfigLastChangedAt.ID) } diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index 477fc24396..79dd1d2d9d 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -6,9 +6,9 @@ package nativeminter import ( "fmt" "math/big" - "math/rand" "testing" + "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" @@ -23,194 +23,198 @@ import ( "go.uber.org/mock/gomock" ) -var tests = map[string]testutils.PrecompileTest{ - "calling mintNativeCoin from NoRole should fail": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestNoRoleAddr, common.Big1) - require.NoError(t, err) +var ( + mintSignature = contract.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotMint.Error(), - }, - "calling mintNativeCoin from Enabled should succeed": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") - }, - }, - "initial mint funds": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - Config: &Config{ - InitialMint: map[common.Address]*math.HexOrDecimal256{ - allowlist.TestEnabledAddr: math.NewHexOrDecimal256(2), + tests = map[string]testutils.PrecompileTest{ + "calling mintNativeCoin from NoRole should fail": { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestNoRoleAddr, common.Big1) + require.NoError(t, err) + + return input }, + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotMint.Error(), }, - AfterHook: func(t testing.TB, state contract.StateDB) { - require.Equal(t, common.Big2, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") - }, - }, - "calling mintNativeCoin from Manager should succeed": { - Caller: allowlist.TestManagerAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") - }, - }, - "calling mintNativeCoin from Admin should succeed": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) - require.NoError(t, err) - - return input + "calling mintNativeCoin from Enabled should succeed": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) + + return input + }, + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") + }, }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds") + "initial mint funds": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + Config: &Config{ + InitialMint: map[common.Address]*math.HexOrDecimal256{ + allowlist.TestEnabledAddr: math.NewHexOrDecimal256(2), + }, + }, + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, common.Big2, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") + }, }, - }, - "mint max big funds": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestAdminAddr, math.MaxBig256) - require.NoError(t, err) - - return input + "calling mintNativeCoin from Manager should succeed": { + Caller: allowlist.TestManagerAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) + + return input + }, + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") + }, }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - require.Equal(t, math.MaxBig256, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds") + "calling mintNativeCoin from Admin should succeed": { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) + require.NoError(t, err) + + return input + }, + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, common.Big1, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds") + }, }, - }, - "readOnly mint with noRole fails": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) - require.NoError(t, err) - - return input + "mint max big funds": { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestAdminAddr, math.MaxBig256) + require.NoError(t, err) + + return input + }, + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, math.MaxBig256, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds") + }, }, - SuppliedGas: MintGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly mint with allow role fails": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - - return input + "readOnly mint with noRole fails": { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) + require.NoError(t, err) + + return input + }, + SuppliedGas: MintGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, - SuppliedGas: MintGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly mint with admin role fails": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) - require.NoError(t, err) - - return input + "readOnly mint with allow role fails": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) + + return input + }, + SuppliedGas: MintGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, - SuppliedGas: MintGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "insufficient gas mint from admin": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - - return input + "readOnly mint with admin role fails": { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) + require.NoError(t, err) + + return input + }, + SuppliedGas: MintGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, - SuppliedGas: MintGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "mint with extra padded bytes should fail before DUpgrade": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - ChainConfigFn: func(t testing.TB) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) - config.EXPECT().IsDUpgrade(gomock.Any()).Return(false).AnyTimes() - return config + "insufficient gas mint from admin": { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) + + return input + }, + SuppliedGas: MintGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) + "mint with extra padded bytes should fail before DUpgrade": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + ChainConfigFn: func(t testing.TB) precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) + config.EXPECT().IsDUpgrade(gomock.Any()).Return(false).AnyTimes() + return config + }, + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) - // Add extra bytes to the end of the input - input = append(input, make([]byte, 32)...) + // Add extra bytes to the end of the input + input = append(input, make([]byte, 32)...) - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedErr: ErrInvalidLen.Error(), - }, - "mint with extra padded bytes should succeed with DUpgrade": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - ChainConfigFn: func(t testing.TB) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) - config.EXPECT().IsDUpgrade(gomock.Any()).Return(true).AnyTimes() - return config + return input + }, + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedErr: ErrInvalidLen.Error(), }, - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) + "mint with extra padded bytes should succeed with DUpgrade": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + ChainConfigFn: func(t testing.TB) precompileconfig.ChainConfig { + config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) + config.EXPECT().IsDUpgrade(gomock.Any()).Return(true).AnyTimes() + return config + }, + InputFn: func(t testing.TB) []byte { + input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) - // Add extra bytes to the end of the input - input = append(input, make([]byte, 32)...) + // Add extra bytes to the end of the input + input = append(input, make([]byte, 32)...) - return input - }, - ExpectedRes: []byte{}, - SuppliedGas: MintGasCost, - ReadOnly: false, - AfterHook: func(t testing.TB, state contract.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") + return input + }, + ExpectedRes: []byte{}, + SuppliedGas: MintGasCost, + ReadOnly: false, + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") + }, }, - }, -} + } +) func TestContractNativeMinterRun(t *testing.T) { allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) @@ -220,24 +224,29 @@ func BenchmarkContractNativeMinter(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } -func TestPackUnpackMintNativeCoinInput(t *testing.T) { - // Test PackMintNativeCoin, UnpackMintNativeCoinInput - // for 1000 random addresses and amounts - for i := 0; i < 1000; i++ { - key, _ := crypto.GenerateKey() - addr := crypto.PubkeyToAddress(key.PublicKey) - amount := new(big.Int).SetInt64(rand.Int63()) - testUnpackAndPacks(t, addr, amount) - } +func addEdgeCases(f *testing.F) *testing.F { + key, err := crypto.GenerateKey() + require.NoError(f, err) + addr := crypto.PubkeyToAddress(key.PublicKey) + testAddrBytes := addr.Bytes() + f.Add(testAddrBytes, common.Big0.Bytes()) + f.Add(testAddrBytes, common.Big1.Bytes()) + f.Add(testAddrBytes, abi.MaxUint256.Bytes()) + f.Add(testAddrBytes, new(big.Int).Sub(abi.MaxUint256, common.Big1).Bytes()) + f.Add(testAddrBytes, new(big.Int).Add(abi.MaxUint256, common.Big1).Bytes()) + f.Add(constants.BlackholeAddr.Bytes(), common.Big2.Bytes()) + return f +} - // Some edge cases - testUnpackAndPacks(t, common.Address{}, common.Big0) - testUnpackAndPacks(t, common.Address{}, common.Big1) - testUnpackAndPacks(t, common.Address{}, math.MaxBig256) - testUnpackAndPacks(t, common.Address{}, math.MaxBig256.Sub(math.MaxBig256, common.Big1)) - testUnpackAndPacks(t, common.Address{}, math.MaxBig256.Add(math.MaxBig256, common.Big1)) - testUnpackAndPacks(t, constants.BlackholeAddr, common.Big2) +func FuzzPackMintNativeCoinEqualTest(f *testing.F) { + f = addEdgeCases(f) + f.Fuzz(func(t *testing.T, b []byte, bigIntBytes []byte) { + bigIntVal := new(big.Int).SetBytes(bigIntBytes) + testOldPackMintNativeCoinEqualTest(t, common.BytesToAddress(b), bigIntVal) + }) +} +func TestUnpackMintNativeCoinInputLenCheck(t *testing.T) { input, err := PackMintNativeCoin(constants.BlackholeAddr, common.Big2) require.NoError(t, err) // exclude 4 bytes for function selector @@ -245,34 +254,66 @@ func TestPackUnpackMintNativeCoinInput(t *testing.T) { // add extra padded bytes input = append(input, make([]byte, 32)...) + _, _, err = OldUnpackMintNativeCoinInput(input) + require.ErrorIs(t, err, ErrInvalidLen) + _, _, err = UnpackMintNativeCoinInput(input, false) require.ErrorIs(t, err, ErrInvalidLen) - addr, amount, err := UnpackMintNativeCoinInput(input, true) + addr, value, err := UnpackMintNativeCoinInput(input, true) require.NoError(t, err) require.Equal(t, constants.BlackholeAddr, addr) - require.Equal(t, common.Big2.Bytes(), amount.Bytes()) + require.Equal(t, common.Big2.Bytes(), value.Bytes()) } -func TestSignatures(t *testing.T) { +func TestFunctionSignatures(t *testing.T) { // Test that the mintNativeCoin signature is correct - mintSignature := contract.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount abiMintNativeCoin := NativeMinterABI.Methods["mintNativeCoin"] require.Equal(t, mintSignature, abiMintNativeCoin.ID) } -func testUnpackAndPacks(t *testing.T, addr common.Address, amount *big.Int) { +func testOldPackMintNativeCoinEqualTest(t *testing.T, addr common.Address, amount *big.Int) { t.Helper() t.Run(fmt.Sprintf("TestUnpackAndPacks, addr: %s, amount: %s", addr.String(), amount.String()), func(t *testing.T) { - input, err := PackMintNativeCoin(addr, amount) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] + input, err := OldPackMintNativeCoinInput(addr, amount) + input2, err2 := PackMintNativeCoin(addr, amount) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.Equal(t, input, input2) - unpackedAddr, unpackedAmount, err := UnpackMintNativeCoinInput(input, true) - require.NoError(t, err) + to, assetAmount, err := OldUnpackMintNativeCoinInput(input) + unpackedAddr, unpackedAmount, err2 := UnpackMintNativeCoinInput(input2, false) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.Equal(t, to, unpackedAddr) + require.Equal(t, assetAmount.Bytes(), unpackedAmount.Bytes()) + }) +} - require.EqualValues(t, addr, unpackedAddr) - require.Equal(t, amount.Bytes(), unpackedAmount.Bytes()) +func OldPackMintNativeCoinInput(address common.Address, amount *big.Int) ([]byte, error) { + // function selector (4 bytes) + input(hash for address + hash for amount) + res := make([]byte, contract.SelectorLen+mintInputLen) + err := contract.PackOrderedHashesWithSelector(res, mintSignature, []common.Hash{ + address.Hash(), + common.BigToHash(amount), }) + + return res, err +} + +func OldUnpackMintNativeCoinInput(input []byte) (common.Address, *big.Int, error) { + mintInputAddressSlot := 0 + mintInputAmountSlot := 1 + if len(input) != mintInputLen { + return common.Address{}, nil, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) + } + to := common.BytesToAddress(contract.PackedHash(input, mintInputAddressSlot)) + assetAmount := new(big.Int).SetBytes(contract.PackedHash(input, mintInputAmountSlot)) + return to, assetAmount, nil } From 2422e076d535e3c7ed942782dd7d52aafa6e0695 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 7 Dec 2023 12:17:45 +0300 Subject: [PATCH 15/19] add get fee config output fuzz tests --- precompile/contracts/feemanager/contract.go | 5 +- .../contracts/feemanager/contract_test.go | 161 +++++++++++++++--- .../contracts/nativeminter/contract_test.go | 32 ++-- 3 files changed, 160 insertions(+), 38 deletions(-) diff --git a/precompile/contracts/feemanager/contract.go b/precompile/contracts/feemanager/contract.go index 03cc91ec76..195ba973e0 100644 --- a/precompile/contracts/feemanager/contract.go +++ b/precompile/contracts/feemanager/contract.go @@ -267,7 +267,10 @@ func PackGetFeeConfigOutput(output commontype.FeeConfig) ([]byte, error) { // UnpackGetFeeConfigOutput attempts to unpack [output] as GetFeeConfigOutput // assumes that [output] does not include selector (omits first 4 func signature bytes) -func UnpackGetFeeConfigOutput(output []byte) (commontype.FeeConfig, error) { +func UnpackGetFeeConfigOutput(output []byte, skipLenCheck bool) (commontype.FeeConfig, error) { + if !skipLenCheck && len(output) != feeConfigInputLen { + return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(output)) + } outputStruct := FeeConfigABIStruct{} err := FeeManagerABI.UnpackIntoInterface(&outputStruct, "getFeeConfig", output) diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index cee31db797..c36a359d20 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -9,6 +9,7 @@ import ( "math/rand" "testing" + "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" @@ -341,44 +342,91 @@ func BenchmarkFeeManager(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } -func TestPackUnpackGetFeeConfigOutput(t *testing.T) { - for i := 0; i < 1000; i++ { +func FuzzPackGetFeeConfigOutputEqualTest(f *testing.F) { + f.Fuzz(func(t *testing.T, bigIntBytes []byte, blockRate uint64) { + bigIntVal := new(big.Int).SetBytes(bigIntBytes) feeConfig := commontype.FeeConfig{ - GasLimit: big.NewInt(rand.Int63()), - TargetBlockRate: rand.Uint64(), - - MinBaseFee: big.NewInt(rand.Int63()), - TargetGas: big.NewInt(rand.Int63()), - BaseFeeChangeDenominator: big.NewInt(rand.Int63()), - - MinBlockGasCost: big.NewInt(rand.Int63()), - MaxBlockGasCost: big.NewInt(rand.Int63()), - BlockGasCostStep: big.NewInt(rand.Int63()), + GasLimit: bigIntVal, + TargetBlockRate: blockRate, + MinBaseFee: bigIntVal, + TargetGas: bigIntVal, + BaseFeeChangeDenominator: bigIntVal, + MinBlockGasCost: bigIntVal, + MaxBlockGasCost: bigIntVal, + BlockGasCostStep: bigIntVal, } + doCheckOutputs := true + // we can only check if outputs are correct if the value is less than MaxUint256 + // otherwise the value will be truncated when packed, + // and thus unpacked output will not be equal to the value + if bigIntVal.Cmp(abi.MaxUint256) > 0 { + doCheckOutputs = false + } + testOldPackGetFeeConfigOutputEqual(t, feeConfig, doCheckOutputs) + }) +} - testGetFeeConfigOutput(t, feeConfig) - } - // Some edge cases - testGetFeeConfigOutput(t, testFeeConfig) - // should panic +func TestPackUnpackGetFeeConfigOutputEdgeCases(t *testing.T) { + testOldPackGetFeeConfigOutputEqual(t, testFeeConfig, true) + // These should panic + require.Panics(t, func() { + _, _ = OldPackFeeConfig(commontype.FeeConfig{}) + }) require.Panics(t, func() { _, _ = PackGetFeeConfigOutput(commontype.FeeConfig{}) }) - _, err := UnpackGetFeeConfigOutput([]byte{}) + unpacked, err := OldUnpackFeeConfig([]byte{}) + require.ErrorIs(t, err, ErrInvalidLen) + unpacked2, err := UnpackGetFeeConfigOutput([]byte{}, false) + require.ErrorIs(t, err, ErrInvalidLen) + require.Equal(t, unpacked, unpacked2) + + _, err = UnpackGetFeeConfigOutput([]byte{}, true) + require.Error(t, err) + + // Test for extra padded bytes + input, err := PackGetFeeConfigOutput(testFeeConfig) + require.NoError(t, err) + // add extra padded bytes + input = append(input, make([]byte, 32)...) + _, err = OldUnpackFeeConfig(input) + require.ErrorIs(t, err, ErrInvalidLen) + _, err = UnpackGetFeeConfigOutput([]byte{}, false) + require.ErrorIs(t, err, ErrInvalidLen) + + _, err = UnpackGetFeeConfigOutput(input, true) + require.NoError(t, err) + + // now it's now divisible by 32 + input = append(input, make([]byte, 1)...) + _, err = UnpackGetFeeConfigOutput(input, true) require.Error(t, err) } -func testGetFeeConfigOutput(t *testing.T, feeConfig commontype.FeeConfig) { +func testOldPackGetFeeConfigOutputEqual(t *testing.T, feeConfig commontype.FeeConfig, checkOutputs bool) { t.Helper() t.Run(fmt.Sprintf("TestGetFeeConfigOutput, feeConfig %v", feeConfig), func(t *testing.T) { - input, err := PackGetFeeConfigOutput(feeConfig) - require.NoError(t, err) - - unpacked, err := UnpackGetFeeConfigOutput(input) - require.NoError(t, err) - - require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + input, err := OldPackFeeConfig(feeConfig) + input2, err2 := PackGetFeeConfigOutput(feeConfig) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.Equal(t, input, input2) + + config, err := OldUnpackFeeConfig(input) + unpacked, err2 := UnpackGetFeeConfigOutput(input, false) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.True(t, config.Equal(&unpacked), "not equal: config %v, unpacked %v", feeConfig, unpacked) + if checkOutputs { + require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + } }) } @@ -433,7 +481,6 @@ func TestPackSetFeeConfigInput(t *testing.T) { testPackSetFeeConfigInput(t, feeConfig) } // Some edge cases - // Some edge cases testPackSetFeeConfigInput(t, testFeeConfig) // These should panic require.Panics(t, func() { @@ -487,3 +534,63 @@ func testPackSetFeeConfigInput(t *testing.T, feeConfig commontype.FeeConfig) { require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) }) } + +func OldPackFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { + return packFeeConfigHelper(feeConfig, false) +} + +func OldUnpackFeeConfig(input []byte) (commontype.FeeConfig, error) { + if len(input) != feeConfigInputLen { + return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) + } + feeConfig := commontype.FeeConfig{} + for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { + listIndex := i - 1 + packedElement := contract.PackedHash(input, listIndex) + switch i { + case gasLimitKey: + feeConfig.GasLimit = new(big.Int).SetBytes(packedElement) + case targetBlockRateKey: + feeConfig.TargetBlockRate = new(big.Int).SetBytes(packedElement).Uint64() + case minBaseFeeKey: + feeConfig.MinBaseFee = new(big.Int).SetBytes(packedElement) + case targetGasKey: + feeConfig.TargetGas = new(big.Int).SetBytes(packedElement) + case baseFeeChangeDenominatorKey: + feeConfig.BaseFeeChangeDenominator = new(big.Int).SetBytes(packedElement) + case minBlockGasCostKey: + feeConfig.MinBlockGasCost = new(big.Int).SetBytes(packedElement) + case maxBlockGasCostKey: + feeConfig.MaxBlockGasCost = new(big.Int).SetBytes(packedElement) + case blockGasCostStepKey: + feeConfig.BlockGasCostStep = new(big.Int).SetBytes(packedElement) + default: + // This should never encounter an unknown fee config key + panic(fmt.Sprintf("unknown fee config key: %d", i)) + } + } + return feeConfig, nil +} + +func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]byte, error) { + hashes := []common.Hash{ + common.BigToHash(feeConfig.GasLimit), + common.BigToHash(new(big.Int).SetUint64(feeConfig.TargetBlockRate)), + common.BigToHash(feeConfig.MinBaseFee), + common.BigToHash(feeConfig.TargetGas), + common.BigToHash(feeConfig.BaseFeeChangeDenominator), + common.BigToHash(feeConfig.MinBlockGasCost), + common.BigToHash(feeConfig.MaxBlockGasCost), + common.BigToHash(feeConfig.BlockGasCostStep), + } + + if useSelector { + res := make([]byte, len(setFeeConfigSignature)+feeConfigInputLen) + err := contract.PackOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) + return res, err + } + + res := make([]byte, len(hashes)*common.HashLength) + err := contract.PackOrderedHashes(res, hashes) + return res, err +} diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index 79dd1d2d9d..5593eff8bc 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -224,7 +224,7 @@ func BenchmarkContractNativeMinter(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } -func addEdgeCases(f *testing.F) *testing.F { +func FuzzPackMintNativeCoinEqualTest(f *testing.F) { key, err := crypto.GenerateKey() require.NoError(f, err) addr := crypto.PubkeyToAddress(key.PublicKey) @@ -235,18 +235,20 @@ func addEdgeCases(f *testing.F) *testing.F { f.Add(testAddrBytes, new(big.Int).Sub(abi.MaxUint256, common.Big1).Bytes()) f.Add(testAddrBytes, new(big.Int).Add(abi.MaxUint256, common.Big1).Bytes()) f.Add(constants.BlackholeAddr.Bytes(), common.Big2.Bytes()) - return f -} - -func FuzzPackMintNativeCoinEqualTest(f *testing.F) { - f = addEdgeCases(f) f.Fuzz(func(t *testing.T, b []byte, bigIntBytes []byte) { bigIntVal := new(big.Int).SetBytes(bigIntBytes) - testOldPackMintNativeCoinEqualTest(t, common.BytesToAddress(b), bigIntVal) + doCheckOutputs := true + // we can only check if outputs are correct if the value is less than MaxUint256 + // otherwise the value will be truncated when packed, + // and thus unpacked output will not be equal to the value + if bigIntVal.Cmp(abi.MaxUint256) > 0 { + doCheckOutputs = false + } + testOldPackMintNativeCoinEqual(t, common.BytesToAddress(b), bigIntVal, doCheckOutputs) }) } -func TestUnpackMintNativeCoinInputLenCheck(t *testing.T) { +func TestUnpackMintNativeCoinInputEdgeCases(t *testing.T) { input, err := PackMintNativeCoin(constants.BlackholeAddr, common.Big2) require.NoError(t, err) // exclude 4 bytes for function selector @@ -264,6 +266,11 @@ func TestUnpackMintNativeCoinInputLenCheck(t *testing.T) { require.NoError(t, err) require.Equal(t, constants.BlackholeAddr, addr) require.Equal(t, common.Big2.Bytes(), value.Bytes()) + + input = append(input, make([]byte, 1)...) + // now it is not divisible by 32 + _, _, err = UnpackMintNativeCoinInput(input, true) + require.Error(t, err) } func TestFunctionSignatures(t *testing.T) { @@ -272,7 +279,7 @@ func TestFunctionSignatures(t *testing.T) { require.Equal(t, mintSignature, abiMintNativeCoin.ID) } -func testOldPackMintNativeCoinEqualTest(t *testing.T, addr common.Address, amount *big.Int) { +func testOldPackMintNativeCoinEqual(t *testing.T, addr common.Address, amount *big.Int, checkOutputs bool) { t.Helper() t.Run(fmt.Sprintf("TestUnpackAndPacks, addr: %s, amount: %s", addr.String(), amount.String()), func(t *testing.T) { input, err := OldPackMintNativeCoinInput(addr, amount) @@ -284,8 +291,9 @@ func testOldPackMintNativeCoinEqualTest(t *testing.T, addr common.Address, amoun require.NoError(t, err2) require.Equal(t, input, input2) + input = input[4:] to, assetAmount, err := OldUnpackMintNativeCoinInput(input) - unpackedAddr, unpackedAmount, err2 := UnpackMintNativeCoinInput(input2, false) + unpackedAddr, unpackedAmount, err2 := UnpackMintNativeCoinInput(input, false) if err != nil { require.ErrorContains(t, err2, err.Error()) return @@ -293,6 +301,10 @@ func testOldPackMintNativeCoinEqualTest(t *testing.T, addr common.Address, amoun require.NoError(t, err2) require.Equal(t, to, unpackedAddr) require.Equal(t, assetAmount.Bytes(), unpackedAmount.Bytes()) + if checkOutputs { + require.Equal(t, addr, to) + require.Equal(t, amount.Bytes(), assetAmount.Bytes()) + } }) } From 74da64c99c24df7ebbbe8f68ffa1faa17b76ad07 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 7 Dec 2023 15:54:15 +0300 Subject: [PATCH 16/19] add fee manager fuzz tests --- .../contracts/feemanager/contract_test.go | 202 +++++++++++++----- 1 file changed, 147 insertions(+), 55 deletions(-) diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index c36a359d20..03e9cd75ca 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -6,7 +6,6 @@ package feemanager import ( "fmt" "math/big" - "math/rand" "testing" "github.com/ava-labs/subnet-evm/accounts/abi" @@ -343,6 +342,12 @@ func BenchmarkFeeManager(b *testing.B) { } func FuzzPackGetFeeConfigOutputEqualTest(f *testing.F) { + f.Add([]byte{}, uint64(0)) + f.Add(big.NewInt(0).Bytes(), uint64(0)) + f.Add(big.NewInt(1).Bytes(), uint64(math.MaxUint64)) + f.Add(math.MaxBig256.Bytes(), uint64(0)) + f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes(), uint64(0)) + f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes(), uint64(0)) f.Fuzz(func(t *testing.T, bigIntBytes []byte, blockRate uint64) { bigIntVal := new(big.Int).SetBytes(bigIntBytes) feeConfig := commontype.FeeConfig{ @@ -404,6 +409,49 @@ func TestPackUnpackGetFeeConfigOutputEdgeCases(t *testing.T) { require.Error(t, err) } +func TestGetFeeConfig(t *testing.T) { + // Compare OldPackGetFeeConfigInput vs PackGetFeeConfig + // to see if they are equivalent + input := OldPackGetFeeConfigInput() + + input2, err := PackGetFeeConfig() + require.NoError(t, err) + + require.Equal(t, input, input2) +} + +func TestGetLastChangedAtInput(t *testing.T) { + // Compare OldPackGetFeeConfigInput vs PackGetFeeConfigLastChangedAt + // to see if they are equivalent + + input := OldPackGetLastChangedAtInput() + + input2, err := PackGetFeeConfigLastChangedAt() + require.NoError(t, err) + + require.Equal(t, input, input2) +} + +func FuzzPackGetLastChangedAtOutput(f *testing.F) { + f.Add([]byte{}) + f.Add(big.NewInt(0).Bytes()) + f.Add(big.NewInt(1).Bytes()) + f.Add(math.MaxBig256.Bytes()) + f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes()) + f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes()) + f.Fuzz(func(t *testing.T, bigIntBytes []byte) { + bigIntVal := new(big.Int).SetBytes(bigIntBytes) + doCheckOutputs := true + // we can only check if outputs are correct if the value is less than MaxUint256 + // otherwise the value will be truncated when packed, + // and thus unpacked output will not be equal to the value + if bigIntVal.Cmp(abi.MaxUint256) > 0 { + doCheckOutputs = false + } + testOldPackGetLastChangedAtOutputEqual(t, bigIntVal, doCheckOutputs) + }) +} + func testOldPackGetFeeConfigOutputEqual(t *testing.T, feeConfig commontype.FeeConfig, checkOutputs bool) { t.Helper() t.Run(fmt.Sprintf("TestGetFeeConfigOutput, feeConfig %v", feeConfig), func(t *testing.T) { @@ -430,63 +478,68 @@ func testOldPackGetFeeConfigOutputEqual(t *testing.T, feeConfig commontype.FeeCo }) } -func TestGetLastChangedAtOutput(t *testing.T) { - // Compare PackGetFeeConfigLastChangedAtOutputV2 vs PackGetLastChangedAtOutput - // to see if they are equivalent - - for i := 0; i < 1000; i++ { - lastChangedAt := big.NewInt(rand.Int63()) - testGetLastChangedAtOutput(t, lastChangedAt) - } - // Some edge cases - testGetLastChangedAtOutput(t, big.NewInt(0)) - testGetLastChangedAtOutput(t, big.NewInt(1)) - testGetLastChangedAtOutput(t, big.NewInt(2)) - testGetLastChangedAtOutput(t, math.MaxBig256) - testGetLastChangedAtOutput(t, math.MaxBig256.Sub(math.MaxBig256, common.Big1)) - testGetLastChangedAtOutput(t, math.MaxBig256.Add(math.MaxBig256, common.Big1)) -} - -func testGetLastChangedAtOutput(t *testing.T, lastChangedAt *big.Int) { +func testOldPackGetLastChangedAtOutputEqual(t *testing.T, blockNumber *big.Int, checkOutputs bool) { t.Helper() - t.Run(fmt.Sprintf("TestGetLastChangedAtOutput, lastChangedAt %v", lastChangedAt), func(t *testing.T) { - // Test PackGetFeeConfigLastChangedAtOutputV2, UnpackGetFeeConfigLastChangedAtOutputV2 - input, err := PackGetFeeConfigLastChangedAtOutput(lastChangedAt) - require.NoError(t, err) - - unpacked, err := UnpackGetFeeConfigLastChangedAtOutput(input) - require.NoError(t, err) + t.Run(fmt.Sprintf("TestGetLastChangedAtOutput, blockNumber %v", blockNumber), func(t *testing.T) { + input := OldPackGetLastChangedAtOutput(blockNumber) + input2, err2 := PackGetFeeConfigLastChangedAtOutput(blockNumber) + require.NoError(t, err2) + require.Equal(t, input, input2) - require.Zero(t, lastChangedAt.Cmp(unpacked), "not equal: lastChangedAt %v, unpacked %v", lastChangedAt, unpacked) + value, err := OldUnpackGetLastChangedAtOutput(input) + unpacked, err2 := UnpackGetFeeConfigLastChangedAtOutput(input) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.True(t, value.Cmp(unpacked) == 0, "not equal: value %v, unpacked %v", value, unpacked) + if checkOutputs { + require.True(t, blockNumber.Cmp(unpacked) == 0, "not equal: blockNumber %v, unpacked %v", blockNumber, unpacked) + } }) } -func TestPackSetFeeConfigInput(t *testing.T) { - // Compare PackSetFeeConfigV2 vs PackSetFeeConfig - // to see if they are equivalent - for i := 0; i < 1000; i++ { +func FuzzPackSetFeeConfigEqualTest(f *testing.F) { + f.Add([]byte{}, uint64(0)) + f.Add(big.NewInt(0).Bytes(), uint64(0)) + f.Add(big.NewInt(1).Bytes(), uint64(math.MaxUint64)) + f.Add(math.MaxBig256.Bytes(), uint64(0)) + f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes(), uint64(0)) + f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes(), uint64(0)) + f.Fuzz(func(t *testing.T, bigIntBytes []byte, blockRate uint64) { + bigIntVal := new(big.Int).SetBytes(bigIntBytes) feeConfig := commontype.FeeConfig{ - GasLimit: big.NewInt(rand.Int63()), - TargetBlockRate: rand.Uint64(), - - MinBaseFee: big.NewInt(rand.Int63()), - TargetGas: big.NewInt(rand.Int63()), - BaseFeeChangeDenominator: big.NewInt(rand.Int63()), - - MinBlockGasCost: big.NewInt(rand.Int63()), - MaxBlockGasCost: big.NewInt(rand.Int63()), - BlockGasCostStep: big.NewInt(rand.Int63()), + GasLimit: bigIntVal, + TargetBlockRate: blockRate, + MinBaseFee: bigIntVal, + TargetGas: bigIntVal, + BaseFeeChangeDenominator: bigIntVal, + MinBlockGasCost: bigIntVal, + MaxBlockGasCost: bigIntVal, + BlockGasCostStep: bigIntVal, + } + doCheckOutputs := true + // we can only check if outputs are correct if the value is less than MaxUint256 + // otherwise the value will be truncated when packed, + // and thus unpacked output will not be equal to the value + if bigIntVal.Cmp(abi.MaxUint256) > 0 { + doCheckOutputs = false } + testOldPackSetFeeConfigInputEqual(t, feeConfig, doCheckOutputs) + }) +} - testPackSetFeeConfigInput(t, feeConfig) - } +func TestPackSetFeeConfigInputEdgeCases(t *testing.T) { // Some edge cases - testPackSetFeeConfigInput(t, testFeeConfig) + testOldPackSetFeeConfigInputEqual(t, testFeeConfig, true) // These should panic + require.Panics(t, func() { + _, _ = OldPackSetFeeConfig(commontype.FeeConfig{}) + }) require.Panics(t, func() { _, _ = PackSetFeeConfig(commontype.FeeConfig{}) }) - // These should err _, err := UnpackSetFeeConfigInput([]byte{123}, false) require.ErrorIs(t, err, ErrInvalidLen) @@ -494,6 +547,9 @@ func TestPackSetFeeConfigInput(t *testing.T) { _, err = UnpackSetFeeConfigInput([]byte{123}, true) require.ErrorContains(t, err, "abi: improperly formatted input") + _, err = OldUnpackFeeConfig([]byte{123}) + require.ErrorIs(t, err, ErrInvalidLen) + // Test for extra padded bytes input, err := PackSetFeeConfig(testFeeConfig) require.NoError(t, err) @@ -501,6 +557,8 @@ func TestPackSetFeeConfigInput(t *testing.T) { input = input[4:] // add extra padded bytes input = append(input, make([]byte, 32)...) + _, err = OldUnpackFeeConfig(input) + require.ErrorIs(t, err, ErrInvalidLen) _, err = UnpackSetFeeConfigInput(input, false) require.ErrorIs(t, err, ErrInvalidLen) @@ -520,18 +578,29 @@ func TestFunctionSignatures(t *testing.T) { require.Equal(t, getFeeConfigLastChangedAtSignature, abiGetFeeConfigLastChangedAt.ID) } -func testPackSetFeeConfigInput(t *testing.T, feeConfig commontype.FeeConfig) { +func testOldPackSetFeeConfigInputEqual(t *testing.T, feeConfig commontype.FeeConfig, checkOutputs bool) { t.Helper() - t.Run(fmt.Sprintf("TestPackSetFeeConfigInput, feeConfig %v", feeConfig), func(t *testing.T) { - input, err := PackSetFeeConfig(feeConfig) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] - - unpacked, err := UnpackSetFeeConfigInput(input, true) - require.NoError(t, err) + t.Run(fmt.Sprintf("TestSetFeeConfigInput, feeConfig %v", feeConfig), func(t *testing.T) { + input, err := OldPackSetFeeConfig(feeConfig) + input2, err2 := PackSetFeeConfig(feeConfig) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.Equal(t, input, input2) - require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + value, err := OldUnpackFeeConfig(input) + unpacked, err2 := UnpackSetFeeConfigInput(input, false) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.True(t, value.Equal(&unpacked), "not equal: value %v, unpacked %v", value, unpacked) + if checkOutputs { + require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + } }) } @@ -594,3 +663,26 @@ func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]by err := contract.PackOrderedHashes(res, hashes) return res, err } + +// PackGetFeeConfigInput packs the getFeeConfig signature +func OldPackGetFeeConfigInput() []byte { + return getFeeConfigSignature +} + +// PackGetLastChangedAtInput packs the getFeeConfigLastChangedAt signature +func OldPackGetLastChangedAtInput() []byte { + return getFeeConfigLastChangedAtSignature +} + +func OldPackGetLastChangedAtOutput(lastChangedAt *big.Int) []byte { + return common.BigToHash(lastChangedAt).Bytes() +} + +func OldUnpackGetLastChangedAtOutput(input []byte) (*big.Int, error) { + return new(big.Int).SetBytes(input), nil +} + +func OldPackSetFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { + // function selector (4 bytes) + input(feeConfig) + return packFeeConfigHelper(feeConfig, true) +} From 6ef61a6a0545f7165250b514008a2e53cf45657e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 7 Dec 2023 16:00:59 +0300 Subject: [PATCH 17/19] move tests to another test file --- .../contracts/feemanager/contract_test.go | 349 ----------------- .../contracts/feemanager/unpack_pack_test.go | 363 ++++++++++++++++++ .../contracts/nativeminter/contract_test.go | 111 ------ .../nativeminter/unpack_pack_test.go | 123 ++++++ 4 files changed, 486 insertions(+), 460 deletions(-) create mode 100644 precompile/contracts/feemanager/unpack_pack_test.go create mode 100644 precompile/contracts/nativeminter/unpack_pack_test.go diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index 03e9cd75ca..d7904b66c8 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -4,11 +4,9 @@ package feemanager import ( - "fmt" "math/big" "testing" - "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" @@ -17,7 +15,6 @@ import ( "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) @@ -340,349 +337,3 @@ func TestFeeManager(t *testing.T) { func BenchmarkFeeManager(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } - -func FuzzPackGetFeeConfigOutputEqualTest(f *testing.F) { - f.Add([]byte{}, uint64(0)) - f.Add(big.NewInt(0).Bytes(), uint64(0)) - f.Add(big.NewInt(1).Bytes(), uint64(math.MaxUint64)) - f.Add(math.MaxBig256.Bytes(), uint64(0)) - f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes(), uint64(0)) - f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes(), uint64(0)) - f.Fuzz(func(t *testing.T, bigIntBytes []byte, blockRate uint64) { - bigIntVal := new(big.Int).SetBytes(bigIntBytes) - feeConfig := commontype.FeeConfig{ - GasLimit: bigIntVal, - TargetBlockRate: blockRate, - MinBaseFee: bigIntVal, - TargetGas: bigIntVal, - BaseFeeChangeDenominator: bigIntVal, - MinBlockGasCost: bigIntVal, - MaxBlockGasCost: bigIntVal, - BlockGasCostStep: bigIntVal, - } - doCheckOutputs := true - // we can only check if outputs are correct if the value is less than MaxUint256 - // otherwise the value will be truncated when packed, - // and thus unpacked output will not be equal to the value - if bigIntVal.Cmp(abi.MaxUint256) > 0 { - doCheckOutputs = false - } - testOldPackGetFeeConfigOutputEqual(t, feeConfig, doCheckOutputs) - }) -} - -func TestPackUnpackGetFeeConfigOutputEdgeCases(t *testing.T) { - testOldPackGetFeeConfigOutputEqual(t, testFeeConfig, true) - // These should panic - require.Panics(t, func() { - _, _ = OldPackFeeConfig(commontype.FeeConfig{}) - }) - require.Panics(t, func() { - _, _ = PackGetFeeConfigOutput(commontype.FeeConfig{}) - }) - - unpacked, err := OldUnpackFeeConfig([]byte{}) - require.ErrorIs(t, err, ErrInvalidLen) - unpacked2, err := UnpackGetFeeConfigOutput([]byte{}, false) - require.ErrorIs(t, err, ErrInvalidLen) - require.Equal(t, unpacked, unpacked2) - - _, err = UnpackGetFeeConfigOutput([]byte{}, true) - require.Error(t, err) - - // Test for extra padded bytes - input, err := PackGetFeeConfigOutput(testFeeConfig) - require.NoError(t, err) - // add extra padded bytes - input = append(input, make([]byte, 32)...) - _, err = OldUnpackFeeConfig(input) - require.ErrorIs(t, err, ErrInvalidLen) - _, err = UnpackGetFeeConfigOutput([]byte{}, false) - require.ErrorIs(t, err, ErrInvalidLen) - - _, err = UnpackGetFeeConfigOutput(input, true) - require.NoError(t, err) - - // now it's now divisible by 32 - input = append(input, make([]byte, 1)...) - _, err = UnpackGetFeeConfigOutput(input, true) - require.Error(t, err) -} - -func TestGetFeeConfig(t *testing.T) { - // Compare OldPackGetFeeConfigInput vs PackGetFeeConfig - // to see if they are equivalent - input := OldPackGetFeeConfigInput() - - input2, err := PackGetFeeConfig() - require.NoError(t, err) - - require.Equal(t, input, input2) -} - -func TestGetLastChangedAtInput(t *testing.T) { - // Compare OldPackGetFeeConfigInput vs PackGetFeeConfigLastChangedAt - // to see if they are equivalent - - input := OldPackGetLastChangedAtInput() - - input2, err := PackGetFeeConfigLastChangedAt() - require.NoError(t, err) - - require.Equal(t, input, input2) -} - -func FuzzPackGetLastChangedAtOutput(f *testing.F) { - f.Add([]byte{}) - f.Add(big.NewInt(0).Bytes()) - f.Add(big.NewInt(1).Bytes()) - f.Add(math.MaxBig256.Bytes()) - f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes()) - f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes()) - f.Fuzz(func(t *testing.T, bigIntBytes []byte) { - bigIntVal := new(big.Int).SetBytes(bigIntBytes) - doCheckOutputs := true - // we can only check if outputs are correct if the value is less than MaxUint256 - // otherwise the value will be truncated when packed, - // and thus unpacked output will not be equal to the value - if bigIntVal.Cmp(abi.MaxUint256) > 0 { - doCheckOutputs = false - } - testOldPackGetLastChangedAtOutputEqual(t, bigIntVal, doCheckOutputs) - }) -} - -func testOldPackGetFeeConfigOutputEqual(t *testing.T, feeConfig commontype.FeeConfig, checkOutputs bool) { - t.Helper() - t.Run(fmt.Sprintf("TestGetFeeConfigOutput, feeConfig %v", feeConfig), func(t *testing.T) { - input, err := OldPackFeeConfig(feeConfig) - input2, err2 := PackGetFeeConfigOutput(feeConfig) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.Equal(t, input, input2) - - config, err := OldUnpackFeeConfig(input) - unpacked, err2 := UnpackGetFeeConfigOutput(input, false) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.True(t, config.Equal(&unpacked), "not equal: config %v, unpacked %v", feeConfig, unpacked) - if checkOutputs { - require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) - } - }) -} - -func testOldPackGetLastChangedAtOutputEqual(t *testing.T, blockNumber *big.Int, checkOutputs bool) { - t.Helper() - t.Run(fmt.Sprintf("TestGetLastChangedAtOutput, blockNumber %v", blockNumber), func(t *testing.T) { - input := OldPackGetLastChangedAtOutput(blockNumber) - input2, err2 := PackGetFeeConfigLastChangedAtOutput(blockNumber) - require.NoError(t, err2) - require.Equal(t, input, input2) - - value, err := OldUnpackGetLastChangedAtOutput(input) - unpacked, err2 := UnpackGetFeeConfigLastChangedAtOutput(input) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.True(t, value.Cmp(unpacked) == 0, "not equal: value %v, unpacked %v", value, unpacked) - if checkOutputs { - require.True(t, blockNumber.Cmp(unpacked) == 0, "not equal: blockNumber %v, unpacked %v", blockNumber, unpacked) - } - }) -} - -func FuzzPackSetFeeConfigEqualTest(f *testing.F) { - f.Add([]byte{}, uint64(0)) - f.Add(big.NewInt(0).Bytes(), uint64(0)) - f.Add(big.NewInt(1).Bytes(), uint64(math.MaxUint64)) - f.Add(math.MaxBig256.Bytes(), uint64(0)) - f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes(), uint64(0)) - f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes(), uint64(0)) - f.Fuzz(func(t *testing.T, bigIntBytes []byte, blockRate uint64) { - bigIntVal := new(big.Int).SetBytes(bigIntBytes) - feeConfig := commontype.FeeConfig{ - GasLimit: bigIntVal, - TargetBlockRate: blockRate, - MinBaseFee: bigIntVal, - TargetGas: bigIntVal, - BaseFeeChangeDenominator: bigIntVal, - MinBlockGasCost: bigIntVal, - MaxBlockGasCost: bigIntVal, - BlockGasCostStep: bigIntVal, - } - doCheckOutputs := true - // we can only check if outputs are correct if the value is less than MaxUint256 - // otherwise the value will be truncated when packed, - // and thus unpacked output will not be equal to the value - if bigIntVal.Cmp(abi.MaxUint256) > 0 { - doCheckOutputs = false - } - testOldPackSetFeeConfigInputEqual(t, feeConfig, doCheckOutputs) - }) -} - -func TestPackSetFeeConfigInputEdgeCases(t *testing.T) { - // Some edge cases - testOldPackSetFeeConfigInputEqual(t, testFeeConfig, true) - // These should panic - require.Panics(t, func() { - _, _ = OldPackSetFeeConfig(commontype.FeeConfig{}) - }) - require.Panics(t, func() { - _, _ = PackSetFeeConfig(commontype.FeeConfig{}) - }) - // These should err - _, err := UnpackSetFeeConfigInput([]byte{123}, false) - require.ErrorIs(t, err, ErrInvalidLen) - - _, err = UnpackSetFeeConfigInput([]byte{123}, true) - require.ErrorContains(t, err, "abi: improperly formatted input") - - _, err = OldUnpackFeeConfig([]byte{123}) - require.ErrorIs(t, err, ErrInvalidLen) - - // Test for extra padded bytes - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] - // add extra padded bytes - input = append(input, make([]byte, 32)...) - _, err = OldUnpackFeeConfig(input) - require.ErrorIs(t, err, ErrInvalidLen) - _, err = UnpackSetFeeConfigInput(input, false) - require.ErrorIs(t, err, ErrInvalidLen) - - unpacked, err := UnpackSetFeeConfigInput(input, true) - require.NoError(t, err) - require.True(t, testFeeConfig.Equal(&unpacked)) -} - -func TestFunctionSignatures(t *testing.T) { - abiSetFeeConfig := FeeManagerABI.Methods["setFeeConfig"] - require.Equal(t, setFeeConfigSignature, abiSetFeeConfig.ID) - - abiGetFeeConfig := FeeManagerABI.Methods["getFeeConfig"] - require.Equal(t, getFeeConfigSignature, abiGetFeeConfig.ID) - - abiGetFeeConfigLastChangedAt := FeeManagerABI.Methods["getFeeConfigLastChangedAt"] - require.Equal(t, getFeeConfigLastChangedAtSignature, abiGetFeeConfigLastChangedAt.ID) -} - -func testOldPackSetFeeConfigInputEqual(t *testing.T, feeConfig commontype.FeeConfig, checkOutputs bool) { - t.Helper() - t.Run(fmt.Sprintf("TestSetFeeConfigInput, feeConfig %v", feeConfig), func(t *testing.T) { - input, err := OldPackSetFeeConfig(feeConfig) - input2, err2 := PackSetFeeConfig(feeConfig) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.Equal(t, input, input2) - - value, err := OldUnpackFeeConfig(input) - unpacked, err2 := UnpackSetFeeConfigInput(input, false) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.True(t, value.Equal(&unpacked), "not equal: value %v, unpacked %v", value, unpacked) - if checkOutputs { - require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) - } - }) -} - -func OldPackFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { - return packFeeConfigHelper(feeConfig, false) -} - -func OldUnpackFeeConfig(input []byte) (commontype.FeeConfig, error) { - if len(input) != feeConfigInputLen { - return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) - } - feeConfig := commontype.FeeConfig{} - for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { - listIndex := i - 1 - packedElement := contract.PackedHash(input, listIndex) - switch i { - case gasLimitKey: - feeConfig.GasLimit = new(big.Int).SetBytes(packedElement) - case targetBlockRateKey: - feeConfig.TargetBlockRate = new(big.Int).SetBytes(packedElement).Uint64() - case minBaseFeeKey: - feeConfig.MinBaseFee = new(big.Int).SetBytes(packedElement) - case targetGasKey: - feeConfig.TargetGas = new(big.Int).SetBytes(packedElement) - case baseFeeChangeDenominatorKey: - feeConfig.BaseFeeChangeDenominator = new(big.Int).SetBytes(packedElement) - case minBlockGasCostKey: - feeConfig.MinBlockGasCost = new(big.Int).SetBytes(packedElement) - case maxBlockGasCostKey: - feeConfig.MaxBlockGasCost = new(big.Int).SetBytes(packedElement) - case blockGasCostStepKey: - feeConfig.BlockGasCostStep = new(big.Int).SetBytes(packedElement) - default: - // This should never encounter an unknown fee config key - panic(fmt.Sprintf("unknown fee config key: %d", i)) - } - } - return feeConfig, nil -} - -func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]byte, error) { - hashes := []common.Hash{ - common.BigToHash(feeConfig.GasLimit), - common.BigToHash(new(big.Int).SetUint64(feeConfig.TargetBlockRate)), - common.BigToHash(feeConfig.MinBaseFee), - common.BigToHash(feeConfig.TargetGas), - common.BigToHash(feeConfig.BaseFeeChangeDenominator), - common.BigToHash(feeConfig.MinBlockGasCost), - common.BigToHash(feeConfig.MaxBlockGasCost), - common.BigToHash(feeConfig.BlockGasCostStep), - } - - if useSelector { - res := make([]byte, len(setFeeConfigSignature)+feeConfigInputLen) - err := contract.PackOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) - return res, err - } - - res := make([]byte, len(hashes)*common.HashLength) - err := contract.PackOrderedHashes(res, hashes) - return res, err -} - -// PackGetFeeConfigInput packs the getFeeConfig signature -func OldPackGetFeeConfigInput() []byte { - return getFeeConfigSignature -} - -// PackGetLastChangedAtInput packs the getFeeConfigLastChangedAt signature -func OldPackGetLastChangedAtInput() []byte { - return getFeeConfigLastChangedAtSignature -} - -func OldPackGetLastChangedAtOutput(lastChangedAt *big.Int) []byte { - return common.BigToHash(lastChangedAt).Bytes() -} - -func OldUnpackGetLastChangedAtOutput(input []byte) (*big.Int, error) { - return new(big.Int).SetBytes(input), nil -} - -func OldPackSetFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { - // function selector (4 bytes) + input(feeConfig) - return packFeeConfigHelper(feeConfig, true) -} diff --git a/precompile/contracts/feemanager/unpack_pack_test.go b/precompile/contracts/feemanager/unpack_pack_test.go new file mode 100644 index 0000000000..61e1a15546 --- /dev/null +++ b/precompile/contracts/feemanager/unpack_pack_test.go @@ -0,0 +1,363 @@ +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package feemanager + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/stretchr/testify/require" +) + +func FuzzPackGetFeeConfigOutputEqualTest(f *testing.F) { + f.Add([]byte{}, uint64(0)) + f.Add(big.NewInt(0).Bytes(), uint64(0)) + f.Add(big.NewInt(1).Bytes(), uint64(math.MaxUint64)) + f.Add(math.MaxBig256.Bytes(), uint64(0)) + f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes(), uint64(0)) + f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes(), uint64(0)) + f.Fuzz(func(t *testing.T, bigIntBytes []byte, blockRate uint64) { + bigIntVal := new(big.Int).SetBytes(bigIntBytes) + feeConfig := commontype.FeeConfig{ + GasLimit: bigIntVal, + TargetBlockRate: blockRate, + MinBaseFee: bigIntVal, + TargetGas: bigIntVal, + BaseFeeChangeDenominator: bigIntVal, + MinBlockGasCost: bigIntVal, + MaxBlockGasCost: bigIntVal, + BlockGasCostStep: bigIntVal, + } + doCheckOutputs := true + // we can only check if outputs are correct if the value is less than MaxUint256 + // otherwise the value will be truncated when packed, + // and thus unpacked output will not be equal to the value + if bigIntVal.Cmp(abi.MaxUint256) > 0 { + doCheckOutputs = false + } + testOldPackGetFeeConfigOutputEqual(t, feeConfig, doCheckOutputs) + }) +} + +func TestPackUnpackGetFeeConfigOutputEdgeCases(t *testing.T) { + testOldPackGetFeeConfigOutputEqual(t, testFeeConfig, true) + // These should panic + require.Panics(t, func() { + _, _ = OldPackFeeConfig(commontype.FeeConfig{}) + }) + require.Panics(t, func() { + _, _ = PackGetFeeConfigOutput(commontype.FeeConfig{}) + }) + + unpacked, err := OldUnpackFeeConfig([]byte{}) + require.ErrorIs(t, err, ErrInvalidLen) + unpacked2, err := UnpackGetFeeConfigOutput([]byte{}, false) + require.ErrorIs(t, err, ErrInvalidLen) + require.Equal(t, unpacked, unpacked2) + + _, err = UnpackGetFeeConfigOutput([]byte{}, true) + require.Error(t, err) + + // Test for extra padded bytes + input, err := PackGetFeeConfigOutput(testFeeConfig) + require.NoError(t, err) + // add extra padded bytes + input = append(input, make([]byte, 32)...) + _, err = OldUnpackFeeConfig(input) + require.ErrorIs(t, err, ErrInvalidLen) + _, err = UnpackGetFeeConfigOutput([]byte{}, false) + require.ErrorIs(t, err, ErrInvalidLen) + + _, err = UnpackGetFeeConfigOutput(input, true) + require.NoError(t, err) + + // now it's now divisible by 32 + input = append(input, make([]byte, 1)...) + _, err = UnpackGetFeeConfigOutput(input, true) + require.Error(t, err) +} + +func TestGetFeeConfig(t *testing.T) { + // Compare OldPackGetFeeConfigInput vs PackGetFeeConfig + // to see if they are equivalent + input := OldPackGetFeeConfigInput() + + input2, err := PackGetFeeConfig() + require.NoError(t, err) + + require.Equal(t, input, input2) +} + +func TestGetLastChangedAtInput(t *testing.T) { + // Compare OldPackGetFeeConfigInput vs PackGetFeeConfigLastChangedAt + // to see if they are equivalent + + input := OldPackGetLastChangedAtInput() + + input2, err := PackGetFeeConfigLastChangedAt() + require.NoError(t, err) + + require.Equal(t, input, input2) +} + +func FuzzPackGetLastChangedAtOutput(f *testing.F) { + f.Add([]byte{}) + f.Add(big.NewInt(0).Bytes()) + f.Add(big.NewInt(1).Bytes()) + f.Add(math.MaxBig256.Bytes()) + f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes()) + f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes()) + f.Fuzz(func(t *testing.T, bigIntBytes []byte) { + bigIntVal := new(big.Int).SetBytes(bigIntBytes) + doCheckOutputs := true + // we can only check if outputs are correct if the value is less than MaxUint256 + // otherwise the value will be truncated when packed, + // and thus unpacked output will not be equal to the value + if bigIntVal.Cmp(abi.MaxUint256) > 0 { + doCheckOutputs = false + } + testOldPackGetLastChangedAtOutputEqual(t, bigIntVal, doCheckOutputs) + }) +} + +func FuzzPackSetFeeConfigEqualTest(f *testing.F) { + f.Add([]byte{}, uint64(0)) + f.Add(big.NewInt(0).Bytes(), uint64(0)) + f.Add(big.NewInt(1).Bytes(), uint64(math.MaxUint64)) + f.Add(math.MaxBig256.Bytes(), uint64(0)) + f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes(), uint64(0)) + f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes(), uint64(0)) + f.Fuzz(func(t *testing.T, bigIntBytes []byte, blockRate uint64) { + bigIntVal := new(big.Int).SetBytes(bigIntBytes) + feeConfig := commontype.FeeConfig{ + GasLimit: bigIntVal, + TargetBlockRate: blockRate, + MinBaseFee: bigIntVal, + TargetGas: bigIntVal, + BaseFeeChangeDenominator: bigIntVal, + MinBlockGasCost: bigIntVal, + MaxBlockGasCost: bigIntVal, + BlockGasCostStep: bigIntVal, + } + doCheckOutputs := true + // we can only check if outputs are correct if the value is less than MaxUint256 + // otherwise the value will be truncated when packed, + // and thus unpacked output will not be equal to the value + if bigIntVal.Cmp(abi.MaxUint256) > 0 { + doCheckOutputs = false + } + testOldPackSetFeeConfigInputEqual(t, feeConfig, doCheckOutputs) + }) +} + +func TestPackSetFeeConfigInputEdgeCases(t *testing.T) { + // Some edge cases + testOldPackSetFeeConfigInputEqual(t, testFeeConfig, true) + // These should panic + require.Panics(t, func() { + _, _ = OldPackSetFeeConfig(commontype.FeeConfig{}) + }) + require.Panics(t, func() { + _, _ = PackSetFeeConfig(commontype.FeeConfig{}) + }) + // These should err + _, err := UnpackSetFeeConfigInput([]byte{123}, false) + require.ErrorIs(t, err, ErrInvalidLen) + + _, err = UnpackSetFeeConfigInput([]byte{123}, true) + require.ErrorContains(t, err, "abi: improperly formatted input") + + _, err = OldUnpackFeeConfig([]byte{123}) + require.ErrorIs(t, err, ErrInvalidLen) + + // Test for extra padded bytes + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + // add extra padded bytes + input = append(input, make([]byte, 32)...) + _, err = OldUnpackFeeConfig(input) + require.ErrorIs(t, err, ErrInvalidLen) + _, err = UnpackSetFeeConfigInput(input, false) + require.ErrorIs(t, err, ErrInvalidLen) + + unpacked, err := UnpackSetFeeConfigInput(input, true) + require.NoError(t, err) + require.True(t, testFeeConfig.Equal(&unpacked)) +} + +func TestFunctionSignatures(t *testing.T) { + abiSetFeeConfig := FeeManagerABI.Methods["setFeeConfig"] + require.Equal(t, setFeeConfigSignature, abiSetFeeConfig.ID) + + abiGetFeeConfig := FeeManagerABI.Methods["getFeeConfig"] + require.Equal(t, getFeeConfigSignature, abiGetFeeConfig.ID) + + abiGetFeeConfigLastChangedAt := FeeManagerABI.Methods["getFeeConfigLastChangedAt"] + require.Equal(t, getFeeConfigLastChangedAtSignature, abiGetFeeConfigLastChangedAt.ID) +} + +func testOldPackGetFeeConfigOutputEqual(t *testing.T, feeConfig commontype.FeeConfig, checkOutputs bool) { + t.Helper() + t.Run(fmt.Sprintf("TestGetFeeConfigOutput, feeConfig %v", feeConfig), func(t *testing.T) { + input, err := OldPackFeeConfig(feeConfig) + input2, err2 := PackGetFeeConfigOutput(feeConfig) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.Equal(t, input, input2) + + config, err := OldUnpackFeeConfig(input) + unpacked, err2 := UnpackGetFeeConfigOutput(input, false) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.True(t, config.Equal(&unpacked), "not equal: config %v, unpacked %v", feeConfig, unpacked) + if checkOutputs { + require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + } + }) +} + +func testOldPackGetLastChangedAtOutputEqual(t *testing.T, blockNumber *big.Int, checkOutputs bool) { + t.Helper() + t.Run(fmt.Sprintf("TestGetLastChangedAtOutput, blockNumber %v", blockNumber), func(t *testing.T) { + input := OldPackGetLastChangedAtOutput(blockNumber) + input2, err2 := PackGetFeeConfigLastChangedAtOutput(blockNumber) + require.NoError(t, err2) + require.Equal(t, input, input2) + + value, err := OldUnpackGetLastChangedAtOutput(input) + unpacked, err2 := UnpackGetFeeConfigLastChangedAtOutput(input) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.True(t, value.Cmp(unpacked) == 0, "not equal: value %v, unpacked %v", value, unpacked) + if checkOutputs { + require.True(t, blockNumber.Cmp(unpacked) == 0, "not equal: blockNumber %v, unpacked %v", blockNumber, unpacked) + } + }) +} + +func testOldPackSetFeeConfigInputEqual(t *testing.T, feeConfig commontype.FeeConfig, checkOutputs bool) { + t.Helper() + t.Run(fmt.Sprintf("TestSetFeeConfigInput, feeConfig %v", feeConfig), func(t *testing.T) { + input, err := OldPackSetFeeConfig(feeConfig) + input2, err2 := PackSetFeeConfig(feeConfig) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.Equal(t, input, input2) + + value, err := OldUnpackFeeConfig(input) + unpacked, err2 := UnpackSetFeeConfigInput(input, false) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.True(t, value.Equal(&unpacked), "not equal: value %v, unpacked %v", value, unpacked) + if checkOutputs { + require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) + } + }) +} + +func OldPackFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { + return packFeeConfigHelper(feeConfig, false) +} + +func OldUnpackFeeConfig(input []byte) (commontype.FeeConfig, error) { + if len(input) != feeConfigInputLen { + return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) + } + feeConfig := commontype.FeeConfig{} + for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { + listIndex := i - 1 + packedElement := contract.PackedHash(input, listIndex) + switch i { + case gasLimitKey: + feeConfig.GasLimit = new(big.Int).SetBytes(packedElement) + case targetBlockRateKey: + feeConfig.TargetBlockRate = new(big.Int).SetBytes(packedElement).Uint64() + case minBaseFeeKey: + feeConfig.MinBaseFee = new(big.Int).SetBytes(packedElement) + case targetGasKey: + feeConfig.TargetGas = new(big.Int).SetBytes(packedElement) + case baseFeeChangeDenominatorKey: + feeConfig.BaseFeeChangeDenominator = new(big.Int).SetBytes(packedElement) + case minBlockGasCostKey: + feeConfig.MinBlockGasCost = new(big.Int).SetBytes(packedElement) + case maxBlockGasCostKey: + feeConfig.MaxBlockGasCost = new(big.Int).SetBytes(packedElement) + case blockGasCostStepKey: + feeConfig.BlockGasCostStep = new(big.Int).SetBytes(packedElement) + default: + // This should never encounter an unknown fee config key + panic(fmt.Sprintf("unknown fee config key: %d", i)) + } + } + return feeConfig, nil +} + +func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]byte, error) { + hashes := []common.Hash{ + common.BigToHash(feeConfig.GasLimit), + common.BigToHash(new(big.Int).SetUint64(feeConfig.TargetBlockRate)), + common.BigToHash(feeConfig.MinBaseFee), + common.BigToHash(feeConfig.TargetGas), + common.BigToHash(feeConfig.BaseFeeChangeDenominator), + common.BigToHash(feeConfig.MinBlockGasCost), + common.BigToHash(feeConfig.MaxBlockGasCost), + common.BigToHash(feeConfig.BlockGasCostStep), + } + + if useSelector { + res := make([]byte, len(setFeeConfigSignature)+feeConfigInputLen) + err := contract.PackOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) + return res, err + } + + res := make([]byte, len(hashes)*common.HashLength) + err := contract.PackOrderedHashes(res, hashes) + return res, err +} + +// PackGetFeeConfigInput packs the getFeeConfig signature +func OldPackGetFeeConfigInput() []byte { + return getFeeConfigSignature +} + +// PackGetLastChangedAtInput packs the getFeeConfigLastChangedAt signature +func OldPackGetLastChangedAtInput() []byte { + return getFeeConfigLastChangedAtSignature +} + +func OldPackGetLastChangedAtOutput(lastChangedAt *big.Int) []byte { + return common.BigToHash(lastChangedAt).Bytes() +} + +func OldUnpackGetLastChangedAtOutput(input []byte) (*big.Int, error) { + return new(big.Int).SetBytes(input), nil +} + +func OldPackSetFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { + // function selector (4 bytes) + input(feeConfig) + return packFeeConfigHelper(feeConfig, true) +} diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index 5593eff8bc..10a4b11f1e 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -4,12 +4,8 @@ package nativeminter import ( - "fmt" - "math/big" "testing" - "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contract" @@ -18,7 +14,6 @@ import ( "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) @@ -223,109 +218,3 @@ func TestContractNativeMinterRun(t *testing.T) { func BenchmarkContractNativeMinter(b *testing.B) { allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) } - -func FuzzPackMintNativeCoinEqualTest(f *testing.F) { - key, err := crypto.GenerateKey() - require.NoError(f, err) - addr := crypto.PubkeyToAddress(key.PublicKey) - testAddrBytes := addr.Bytes() - f.Add(testAddrBytes, common.Big0.Bytes()) - f.Add(testAddrBytes, common.Big1.Bytes()) - f.Add(testAddrBytes, abi.MaxUint256.Bytes()) - f.Add(testAddrBytes, new(big.Int).Sub(abi.MaxUint256, common.Big1).Bytes()) - f.Add(testAddrBytes, new(big.Int).Add(abi.MaxUint256, common.Big1).Bytes()) - f.Add(constants.BlackholeAddr.Bytes(), common.Big2.Bytes()) - f.Fuzz(func(t *testing.T, b []byte, bigIntBytes []byte) { - bigIntVal := new(big.Int).SetBytes(bigIntBytes) - doCheckOutputs := true - // we can only check if outputs are correct if the value is less than MaxUint256 - // otherwise the value will be truncated when packed, - // and thus unpacked output will not be equal to the value - if bigIntVal.Cmp(abi.MaxUint256) > 0 { - doCheckOutputs = false - } - testOldPackMintNativeCoinEqual(t, common.BytesToAddress(b), bigIntVal, doCheckOutputs) - }) -} - -func TestUnpackMintNativeCoinInputEdgeCases(t *testing.T) { - input, err := PackMintNativeCoin(constants.BlackholeAddr, common.Big2) - require.NoError(t, err) - // exclude 4 bytes for function selector - input = input[4:] - // add extra padded bytes - input = append(input, make([]byte, 32)...) - - _, _, err = OldUnpackMintNativeCoinInput(input) - require.ErrorIs(t, err, ErrInvalidLen) - - _, _, err = UnpackMintNativeCoinInput(input, false) - require.ErrorIs(t, err, ErrInvalidLen) - - addr, value, err := UnpackMintNativeCoinInput(input, true) - require.NoError(t, err) - require.Equal(t, constants.BlackholeAddr, addr) - require.Equal(t, common.Big2.Bytes(), value.Bytes()) - - input = append(input, make([]byte, 1)...) - // now it is not divisible by 32 - _, _, err = UnpackMintNativeCoinInput(input, true) - require.Error(t, err) -} - -func TestFunctionSignatures(t *testing.T) { - // Test that the mintNativeCoin signature is correct - abiMintNativeCoin := NativeMinterABI.Methods["mintNativeCoin"] - require.Equal(t, mintSignature, abiMintNativeCoin.ID) -} - -func testOldPackMintNativeCoinEqual(t *testing.T, addr common.Address, amount *big.Int, checkOutputs bool) { - t.Helper() - t.Run(fmt.Sprintf("TestUnpackAndPacks, addr: %s, amount: %s", addr.String(), amount.String()), func(t *testing.T) { - input, err := OldPackMintNativeCoinInput(addr, amount) - input2, err2 := PackMintNativeCoin(addr, amount) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.Equal(t, input, input2) - - input = input[4:] - to, assetAmount, err := OldUnpackMintNativeCoinInput(input) - unpackedAddr, unpackedAmount, err2 := UnpackMintNativeCoinInput(input, false) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.Equal(t, to, unpackedAddr) - require.Equal(t, assetAmount.Bytes(), unpackedAmount.Bytes()) - if checkOutputs { - require.Equal(t, addr, to) - require.Equal(t, amount.Bytes(), assetAmount.Bytes()) - } - }) -} - -func OldPackMintNativeCoinInput(address common.Address, amount *big.Int) ([]byte, error) { - // function selector (4 bytes) + input(hash for address + hash for amount) - res := make([]byte, contract.SelectorLen+mintInputLen) - err := contract.PackOrderedHashesWithSelector(res, mintSignature, []common.Hash{ - address.Hash(), - common.BigToHash(amount), - }) - - return res, err -} - -func OldUnpackMintNativeCoinInput(input []byte) (common.Address, *big.Int, error) { - mintInputAddressSlot := 0 - mintInputAmountSlot := 1 - if len(input) != mintInputLen { - return common.Address{}, nil, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) - } - to := common.BytesToAddress(contract.PackedHash(input, mintInputAddressSlot)) - assetAmount := new(big.Int).SetBytes(contract.PackedHash(input, mintInputAmountSlot)) - return to, assetAmount, nil -} diff --git a/precompile/contracts/nativeminter/unpack_pack_test.go b/precompile/contracts/nativeminter/unpack_pack_test.go new file mode 100644 index 0000000000..95e55505a1 --- /dev/null +++ b/precompile/contracts/nativeminter/unpack_pack_test.go @@ -0,0 +1,123 @@ +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package nativeminter + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/constants" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +func FuzzPackMintNativeCoinEqualTest(f *testing.F) { + key, err := crypto.GenerateKey() + require.NoError(f, err) + addr := crypto.PubkeyToAddress(key.PublicKey) + testAddrBytes := addr.Bytes() + f.Add(testAddrBytes, common.Big0.Bytes()) + f.Add(testAddrBytes, common.Big1.Bytes()) + f.Add(testAddrBytes, abi.MaxUint256.Bytes()) + f.Add(testAddrBytes, new(big.Int).Sub(abi.MaxUint256, common.Big1).Bytes()) + f.Add(testAddrBytes, new(big.Int).Add(abi.MaxUint256, common.Big1).Bytes()) + f.Add(constants.BlackholeAddr.Bytes(), common.Big2.Bytes()) + f.Fuzz(func(t *testing.T, b []byte, bigIntBytes []byte) { + bigIntVal := new(big.Int).SetBytes(bigIntBytes) + doCheckOutputs := true + // we can only check if outputs are correct if the value is less than MaxUint256 + // otherwise the value will be truncated when packed, + // and thus unpacked output will not be equal to the value + if bigIntVal.Cmp(abi.MaxUint256) > 0 { + doCheckOutputs = false + } + testOldPackMintNativeCoinEqual(t, common.BytesToAddress(b), bigIntVal, doCheckOutputs) + }) +} + +func TestUnpackMintNativeCoinInputEdgeCases(t *testing.T) { + input, err := PackMintNativeCoin(constants.BlackholeAddr, common.Big2) + require.NoError(t, err) + // exclude 4 bytes for function selector + input = input[4:] + // add extra padded bytes + input = append(input, make([]byte, 32)...) + + _, _, err = OldUnpackMintNativeCoinInput(input) + require.ErrorIs(t, err, ErrInvalidLen) + + _, _, err = UnpackMintNativeCoinInput(input, false) + require.ErrorIs(t, err, ErrInvalidLen) + + addr, value, err := UnpackMintNativeCoinInput(input, true) + require.NoError(t, err) + require.Equal(t, constants.BlackholeAddr, addr) + require.Equal(t, common.Big2.Bytes(), value.Bytes()) + + input = append(input, make([]byte, 1)...) + // now it is not divisible by 32 + _, _, err = UnpackMintNativeCoinInput(input, true) + require.Error(t, err) +} + +func TestFunctionSignatures(t *testing.T) { + // Test that the mintNativeCoin signature is correct + abiMintNativeCoin := NativeMinterABI.Methods["mintNativeCoin"] + require.Equal(t, mintSignature, abiMintNativeCoin.ID) +} + +func testOldPackMintNativeCoinEqual(t *testing.T, addr common.Address, amount *big.Int, checkOutputs bool) { + t.Helper() + t.Run(fmt.Sprintf("TestUnpackAndPacks, addr: %s, amount: %s", addr.String(), amount.String()), func(t *testing.T) { + input, err := OldPackMintNativeCoinInput(addr, amount) + input2, err2 := PackMintNativeCoin(addr, amount) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.Equal(t, input, input2) + + input = input[4:] + to, assetAmount, err := OldUnpackMintNativeCoinInput(input) + unpackedAddr, unpackedAmount, err2 := UnpackMintNativeCoinInput(input, false) + if err != nil { + require.ErrorContains(t, err2, err.Error()) + return + } + require.NoError(t, err2) + require.Equal(t, to, unpackedAddr) + require.Equal(t, assetAmount.Bytes(), unpackedAmount.Bytes()) + if checkOutputs { + require.Equal(t, addr, to) + require.Equal(t, amount.Bytes(), assetAmount.Bytes()) + } + }) +} + +func OldPackMintNativeCoinInput(address common.Address, amount *big.Int) ([]byte, error) { + // function selector (4 bytes) + input(hash for address + hash for amount) + res := make([]byte, contract.SelectorLen+mintInputLen) + err := contract.PackOrderedHashesWithSelector(res, mintSignature, []common.Hash{ + address.Hash(), + common.BigToHash(amount), + }) + + return res, err +} + +func OldUnpackMintNativeCoinInput(input []byte) (common.Address, *big.Int, error) { + mintInputAddressSlot := 0 + mintInputAmountSlot := 1 + if len(input) != mintInputLen { + return common.Address{}, nil, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) + } + to := common.BytesToAddress(contract.PackedHash(input, mintInputAddressSlot)) + assetAmount := new(big.Int).SetBytes(contract.PackedHash(input, mintInputAmountSlot)) + return to, assetAmount, nil +} From 7049cf578d1fd27b5ef519aa975b3d25e732e199 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 7 Dec 2023 22:53:04 +0300 Subject: [PATCH 18/19] move signatures to unpack test --- precompile/contracts/feemanager/contract_test.go | 4 ---- precompile/contracts/feemanager/unpack_pack_test.go | 6 ++++++ precompile/contracts/nativeminter/contract_test.go | 2 -- precompile/contracts/nativeminter/unpack_pack_test.go | 4 ++++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index d7904b66c8..6a51951843 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -20,10 +20,6 @@ import ( ) var ( - setFeeConfigSignature = contract.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") - getFeeConfigSignature = contract.CalculateFunctionSelector("getFeeConfig()") - getFeeConfigLastChangedAtSignature = contract.CalculateFunctionSelector("getFeeConfigLastChangedAt()") - testFeeConfig = commontype.FeeConfig{ GasLimit: big.NewInt(8_000_000), TargetBlockRate: 2, // in seconds diff --git a/precompile/contracts/feemanager/unpack_pack_test.go b/precompile/contracts/feemanager/unpack_pack_test.go index 61e1a15546..adcda4f573 100644 --- a/precompile/contracts/feemanager/unpack_pack_test.go +++ b/precompile/contracts/feemanager/unpack_pack_test.go @@ -16,6 +16,12 @@ import ( "github.com/stretchr/testify/require" ) +var ( + setFeeConfigSignature = contract.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") + getFeeConfigSignature = contract.CalculateFunctionSelector("getFeeConfig()") + getFeeConfigLastChangedAtSignature = contract.CalculateFunctionSelector("getFeeConfigLastChangedAt()") +) + func FuzzPackGetFeeConfigOutputEqualTest(f *testing.F) { f.Add([]byte{}, uint64(0)) f.Add(big.NewInt(0).Bytes(), uint64(0)) diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index 10a4b11f1e..6f6a30a82c 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -19,8 +19,6 @@ import ( ) var ( - mintSignature = contract.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount - tests = map[string]testutils.PrecompileTest{ "calling mintNativeCoin from NoRole should fail": { Caller: allowlist.TestNoRoleAddr, diff --git a/precompile/contracts/nativeminter/unpack_pack_test.go b/precompile/contracts/nativeminter/unpack_pack_test.go index 95e55505a1..6536757bdc 100644 --- a/precompile/contracts/nativeminter/unpack_pack_test.go +++ b/precompile/contracts/nativeminter/unpack_pack_test.go @@ -16,6 +16,10 @@ import ( "github.com/stretchr/testify/require" ) +var ( + mintSignature = contract.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount +) + func FuzzPackMintNativeCoinEqualTest(f *testing.F) { key, err := crypto.GenerateKey() require.NoError(f, err) From 5ba81959ed3be13be5d8fe28b8445db1df255696 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 14 Dec 2023 19:05:29 +0300 Subject: [PATCH 19/19] nits --- .../precompile_module_template.go | 2 +- .../contracts/deployerallowlist/module.go | 2 +- precompile/contracts/feemanager/module.go | 2 +- .../contracts/feemanager/unpack_pack_test.go | 206 +++++++++++++----- precompile/contracts/nativeminter/module.go | 2 +- .../nativeminter/unpack_pack_test.go | 98 +++++++-- precompile/contracts/txallowlist/module.go | 2 +- x/warp/module.go | 2 +- 8 files changed, 237 insertions(+), 79 deletions(-) diff --git a/accounts/abi/bind/precompilebind/precompile_module_template.go b/accounts/abi/bind/precompilebind/precompile_module_template.go index 5821dfd527..e9dd8e7275 100644 --- a/accounts/abi/bind/precompilebind/precompile_module_template.go +++ b/accounts/abi/bind/precompilebind/precompile_module_template.go @@ -50,7 +50,7 @@ func init() { } // MakeConfig returns a new precompile config instance. -// This is required for Marshal/Unmarshal the precompile config. +// This is required to Marshal/Unmarshal the precompile config. func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) } diff --git a/precompile/contracts/deployerallowlist/module.go b/precompile/contracts/deployerallowlist/module.go index 6cf446d3e3..17f7431ab0 100644 --- a/precompile/contracts/deployerallowlist/module.go +++ b/precompile/contracts/deployerallowlist/module.go @@ -36,7 +36,7 @@ func init() { } // MakeConfig returns a new precompile config instance. -// This is required for Marshal/Unmarshal the precompile config. +// This is required to Marshal/Unmarshal the precompile config. func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) } diff --git a/precompile/contracts/feemanager/module.go b/precompile/contracts/feemanager/module.go index dec87368f2..e67e5e1115 100644 --- a/precompile/contracts/feemanager/module.go +++ b/precompile/contracts/feemanager/module.go @@ -39,7 +39,7 @@ func init() { } // MakeConfig returns a new precompile config instance. -// This is required for Marshal/Unmarshal the precompile config. +// This is required to Marshal/Unmarshal the precompile config. func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) } diff --git a/precompile/contracts/feemanager/unpack_pack_test.go b/precompile/contracts/feemanager/unpack_pack_test.go index adcda4f573..d758674ff3 100644 --- a/precompile/contracts/feemanager/unpack_pack_test.go +++ b/precompile/contracts/feemanager/unpack_pack_test.go @@ -52,42 +52,91 @@ func FuzzPackGetFeeConfigOutputEqualTest(f *testing.F) { }) } -func TestPackUnpackGetFeeConfigOutputEdgeCases(t *testing.T) { +func TestOldPackGetFeeConfigOutputEqual(t *testing.T) { testOldPackGetFeeConfigOutputEqual(t, testFeeConfig, true) - // These should panic +} +func TestPackGetFeeConfigOutputPanic(t *testing.T) { require.Panics(t, func() { _, _ = OldPackFeeConfig(commontype.FeeConfig{}) }) require.Panics(t, func() { _, _ = PackGetFeeConfigOutput(commontype.FeeConfig{}) }) +} - unpacked, err := OldUnpackFeeConfig([]byte{}) - require.ErrorIs(t, err, ErrInvalidLen) - unpacked2, err := UnpackGetFeeConfigOutput([]byte{}, false) - require.ErrorIs(t, err, ErrInvalidLen) - require.Equal(t, unpacked, unpacked2) - - _, err = UnpackGetFeeConfigOutput([]byte{}, true) - require.Error(t, err) - - // Test for extra padded bytes - input, err := PackGetFeeConfigOutput(testFeeConfig) +func TestPackGetFeeConfigOutput(t *testing.T) { + testInputBytes, err := PackGetFeeConfigOutput(testFeeConfig) require.NoError(t, err) - // add extra padded bytes - input = append(input, make([]byte, 32)...) - _, err = OldUnpackFeeConfig(input) - require.ErrorIs(t, err, ErrInvalidLen) - _, err = UnpackGetFeeConfigOutput([]byte{}, false) - require.ErrorIs(t, err, ErrInvalidLen) - - _, err = UnpackGetFeeConfigOutput(input, true) - require.NoError(t, err) - - // now it's now divisible by 32 - input = append(input, make([]byte, 1)...) - _, err = UnpackGetFeeConfigOutput(input, true) - require.Error(t, err) + tests := []struct { + name string + input []byte + skipLenCheck bool + expectedErr string + expectedOldErr string + expectedOutput commontype.FeeConfig + }{ + { + name: "empty input", + input: []byte{}, + skipLenCheck: false, + expectedErr: ErrInvalidLen.Error(), + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "empty input skip len check", + input: []byte{}, + skipLenCheck: true, + expectedErr: "attempting to unmarshall an empty string", + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "input with extra bytes", + input: append(testInputBytes, make([]byte, 32)...), + skipLenCheck: false, + expectedErr: ErrInvalidLen.Error(), + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "input with extra bytes skip len check", + input: append(testInputBytes, make([]byte, 32)...), + skipLenCheck: true, + expectedErr: "", + expectedOldErr: ErrInvalidLen.Error(), + expectedOutput: testFeeConfig, + }, + { + name: "input with extra bytes (not divisible by 32)", + input: append(testInputBytes, make([]byte, 33)...), + skipLenCheck: false, + expectedErr: ErrInvalidLen.Error(), + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "input with extra bytes (not divisible by 32) skip len check", + input: append(testInputBytes, make([]byte, 33)...), + skipLenCheck: true, + expectedErr: "improperly formatted output", + expectedOldErr: ErrInvalidLen.Error(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unpacked, err := UnpackGetFeeConfigOutput(test.input, test.skipLenCheck) + if test.expectedErr != "" { + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) + require.True(t, test.expectedOutput.Equal(&unpacked), "not equal: expectedOutput %v, unpacked %v", test.expectedOutput, unpacked) + } + oldUnpacked, oldErr := OldUnpackFeeConfig(test.input) + if test.expectedOldErr != "" { + require.ErrorContains(t, oldErr, test.expectedOldErr) + } else { + require.NoError(t, oldErr) + require.True(t, test.expectedOutput.Equal(&oldUnpacked), "not equal: expectedOutput %v, oldUnpacked %v", test.expectedOutput, oldUnpacked) + } + }) + } } func TestGetFeeConfig(t *testing.T) { @@ -163,41 +212,94 @@ func FuzzPackSetFeeConfigEqualTest(f *testing.F) { }) } -func TestPackSetFeeConfigInputEdgeCases(t *testing.T) { - // Some edge cases +func TestOldPackSetFeeConfigInputEqual(t *testing.T) { testOldPackSetFeeConfigInputEqual(t, testFeeConfig, true) - // These should panic +} + +func TestPackSetFeeConfigInputPanic(t *testing.T) { require.Panics(t, func() { _, _ = OldPackSetFeeConfig(commontype.FeeConfig{}) }) require.Panics(t, func() { _, _ = PackSetFeeConfig(commontype.FeeConfig{}) }) - // These should err - _, err := UnpackSetFeeConfigInput([]byte{123}, false) - require.ErrorIs(t, err, ErrInvalidLen) - - _, err = UnpackSetFeeConfigInput([]byte{123}, true) - require.ErrorContains(t, err, "abi: improperly formatted input") - - _, err = OldUnpackFeeConfig([]byte{123}) - require.ErrorIs(t, err, ErrInvalidLen) +} - // Test for extra padded bytes - input, err := PackSetFeeConfig(testFeeConfig) +func TestPackSetFeeConfigInput(t *testing.T) { + testInputBytes, err := PackSetFeeConfig(testFeeConfig) require.NoError(t, err) // exclude 4 bytes for function selector - input = input[4:] - // add extra padded bytes - input = append(input, make([]byte, 32)...) - _, err = OldUnpackFeeConfig(input) - require.ErrorIs(t, err, ErrInvalidLen) - _, err = UnpackSetFeeConfigInput(input, false) - require.ErrorIs(t, err, ErrInvalidLen) - - unpacked, err := UnpackSetFeeConfigInput(input, true) - require.NoError(t, err) - require.True(t, testFeeConfig.Equal(&unpacked)) + testInputBytes = testInputBytes[4:] + tests := []struct { + name string + input []byte + skipLenCheck bool + expectedErr string + expectedOldErr string + expectedOutput commontype.FeeConfig + }{ + { + name: "empty input", + input: []byte{}, + skipLenCheck: false, + expectedErr: ErrInvalidLen.Error(), + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "empty input skip len check", + input: []byte{}, + skipLenCheck: true, + expectedErr: "attempting to unmarshall an empty string", + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "input with extra bytes", + input: append(testInputBytes, make([]byte, 32)...), + skipLenCheck: false, + expectedErr: ErrInvalidLen.Error(), + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "input with extra bytes skip len check", + input: append(testInputBytes, make([]byte, 32)...), + skipLenCheck: true, + expectedErr: "", + expectedOldErr: ErrInvalidLen.Error(), + expectedOutput: testFeeConfig, + }, + { + name: "input with extra bytes (not divisible by 32)", + input: append(testInputBytes, make([]byte, 33)...), + skipLenCheck: false, + expectedErr: ErrInvalidLen.Error(), + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "input with extra bytes (not divisible by 32) skip len check", + input: append(testInputBytes, make([]byte, 33)...), + skipLenCheck: true, + expectedErr: "improperly formatted input", + expectedOldErr: ErrInvalidLen.Error(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unpacked, err := UnpackSetFeeConfigInput(test.input, test.skipLenCheck) + if test.expectedErr != "" { + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) + require.True(t, test.expectedOutput.Equal(&unpacked), "not equal: expectedOutput %v, unpacked %v", test.expectedOutput, unpacked) + } + oldUnpacked, oldErr := OldUnpackFeeConfig(test.input) + if test.expectedOldErr != "" { + require.ErrorContains(t, oldErr, test.expectedOldErr) + } else { + require.NoError(t, oldErr) + require.True(t, test.expectedOutput.Equal(&oldUnpacked), "not equal: expectedOutput %v, oldUnpacked %v", test.expectedOutput, oldUnpacked) + } + }) + } } func TestFunctionSignatures(t *testing.T) { diff --git a/precompile/contracts/nativeminter/module.go b/precompile/contracts/nativeminter/module.go index 55477c1d0b..ce62cee149 100644 --- a/precompile/contracts/nativeminter/module.go +++ b/precompile/contracts/nativeminter/module.go @@ -38,7 +38,7 @@ func init() { } // MakeConfig returns a new precompile config instance. -// This is required for Marshal/Unmarshal the precompile config. +// This is required to Marshal/Unmarshal the precompile config. func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) } diff --git a/precompile/contracts/nativeminter/unpack_pack_test.go b/precompile/contracts/nativeminter/unpack_pack_test.go index 6536757bdc..860249f118 100644 --- a/precompile/contracts/nativeminter/unpack_pack_test.go +++ b/precompile/contracts/nativeminter/unpack_pack_test.go @@ -44,29 +44,85 @@ func FuzzPackMintNativeCoinEqualTest(f *testing.F) { }) } -func TestUnpackMintNativeCoinInputEdgeCases(t *testing.T) { - input, err := PackMintNativeCoin(constants.BlackholeAddr, common.Big2) +func TestUnpackMintNativeCoinInput(t *testing.T) { + testInputBytes, err := PackMintNativeCoin(constants.BlackholeAddr, common.Big2) require.NoError(t, err) // exclude 4 bytes for function selector - input = input[4:] - // add extra padded bytes - input = append(input, make([]byte, 32)...) - - _, _, err = OldUnpackMintNativeCoinInput(input) - require.ErrorIs(t, err, ErrInvalidLen) - - _, _, err = UnpackMintNativeCoinInput(input, false) - require.ErrorIs(t, err, ErrInvalidLen) - - addr, value, err := UnpackMintNativeCoinInput(input, true) - require.NoError(t, err) - require.Equal(t, constants.BlackholeAddr, addr) - require.Equal(t, common.Big2.Bytes(), value.Bytes()) - - input = append(input, make([]byte, 1)...) - // now it is not divisible by 32 - _, _, err = UnpackMintNativeCoinInput(input, true) - require.Error(t, err) + testInputBytes = testInputBytes[4:] + tests := []struct { + name string + input []byte + skipLenCheck bool + expectedErr string + expectedOldErr string + expectedAddr common.Address + expectedAmount *big.Int + }{ + { + name: "empty input", + input: []byte{}, + skipLenCheck: false, + expectedErr: ErrInvalidLen.Error(), + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "empty input skip len check", + input: []byte{}, + skipLenCheck: true, + expectedErr: "attempting to unmarshall an empty string", + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "input with extra bytes", + input: append(testInputBytes, make([]byte, 32)...), + skipLenCheck: false, + expectedErr: ErrInvalidLen.Error(), + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "input with extra bytes skip len check", + input: append(testInputBytes, make([]byte, 32)...), + skipLenCheck: true, + expectedErr: "", + expectedOldErr: ErrInvalidLen.Error(), + expectedAddr: constants.BlackholeAddr, + expectedAmount: common.Big2, + }, + { + name: "input with extra bytes (not divisible by 32)", + input: append(testInputBytes, make([]byte, 33)...), + skipLenCheck: false, + expectedErr: ErrInvalidLen.Error(), + expectedOldErr: ErrInvalidLen.Error(), + }, + { + name: "input with extra bytes (not divisible by 32) skip len check", + input: append(testInputBytes, make([]byte, 33)...), + skipLenCheck: true, + expectedErr: "improperly formatted input", + expectedOldErr: ErrInvalidLen.Error(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unpackedAddress, unpackedAmount, err := UnpackMintNativeCoinInput(test.input, test.skipLenCheck) + if test.expectedErr != "" { + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) + require.Equal(t, test.expectedAddr, unpackedAddress) + require.True(t, test.expectedAmount.Cmp(unpackedAmount) == 0, "expected %s, got %s", test.expectedAmount.String(), unpackedAmount.String()) + } + oldUnpackedAddress, oldUnpackedAmount, oldErr := OldUnpackMintNativeCoinInput(test.input) + if test.expectedOldErr != "" { + require.ErrorContains(t, oldErr, test.expectedOldErr) + } else { + require.NoError(t, oldErr) + require.Equal(t, test.expectedAddr, oldUnpackedAddress) + require.True(t, test.expectedAmount.Cmp(oldUnpackedAmount) == 0, "expected %s, got %s", test.expectedAmount.String(), oldUnpackedAmount.String()) + } + }) + } } func TestFunctionSignatures(t *testing.T) { diff --git a/precompile/contracts/txallowlist/module.go b/precompile/contracts/txallowlist/module.go index 5b09b13543..f7333613c7 100644 --- a/precompile/contracts/txallowlist/module.go +++ b/precompile/contracts/txallowlist/module.go @@ -36,7 +36,7 @@ func init() { } // MakeConfig returns a new precompile config instance. -// This is required for Marshal/Unmarshal the precompile config. +// This is required to Marshal/Unmarshal the precompile config. func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) } diff --git a/x/warp/module.go b/x/warp/module.go index d09d1ac5ec..37b7451184 100644 --- a/x/warp/module.go +++ b/x/warp/module.go @@ -41,7 +41,7 @@ func init() { } // MakeConfig returns a new precompile config instance. -// This is required for Marshal/Unmarshal the precompile config. +// This is required to Marshal/Unmarshal the precompile config. func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) }