From 1391fb336441d6d1ced9231c7c1d518d985065c5 Mon Sep 17 00:00:00 2001 From: GuillemGarciaDev Date: Tue, 5 Aug 2025 14:59:48 +0200 Subject: [PATCH 1/8] feat(erc20factory): erc20factory precompile --- evmd/precompiles.go | 7 + local_node.sh | 2 +- precompiles/common/interfaces.go | 2 + precompiles/erc20factory/IERC20Factory.sol | 57 +++++ precompiles/erc20factory/abi.json | 139 +++++++++++ precompiles/erc20factory/erc20factory.go | 155 ++++++++++++ precompiles/erc20factory/erc20factory_test.go | 62 +++++ precompiles/erc20factory/events.go | 57 +++++ precompiles/erc20factory/events_test.go | 70 ++++++ precompiles/erc20factory/interfaces.go | 25 ++ precompiles/erc20factory/query.go | 29 +++ precompiles/erc20factory/query_test.go | 75 ++++++ precompiles/erc20factory/setup_test.go | 61 +++++ precompiles/erc20factory/tx.go | 140 +++++++++++ precompiles/erc20factory/tx_test.go | 172 ++++++++++++++ precompiles/erc20factory/types.go | 101 ++++++++ precompiles/erc20factory/types_test.go | 223 ++++++++++++++++++ precompiles/erc20factory/utils_test.go | 56 +++++ tests/solidity/init-node.sh | 2 +- .../suites/precompiles/test/erc20factory.js | 62 +++++ x/precisebank/keeper/keeper.go | 9 + 21 files changed, 1504 insertions(+), 2 deletions(-) create mode 100644 precompiles/erc20factory/IERC20Factory.sol create mode 100644 precompiles/erc20factory/abi.json create mode 100644 precompiles/erc20factory/erc20factory.go create mode 100644 precompiles/erc20factory/erc20factory_test.go create mode 100644 precompiles/erc20factory/events.go create mode 100644 precompiles/erc20factory/events_test.go create mode 100644 precompiles/erc20factory/interfaces.go create mode 100644 precompiles/erc20factory/query.go create mode 100644 precompiles/erc20factory/query_test.go create mode 100644 precompiles/erc20factory/setup_test.go create mode 100644 precompiles/erc20factory/tx.go create mode 100644 precompiles/erc20factory/tx_test.go create mode 100644 precompiles/erc20factory/types.go create mode 100644 precompiles/erc20factory/types_test.go create mode 100644 precompiles/erc20factory/utils_test.go create mode 100644 tests/solidity/suites/precompiles/test/erc20factory.js diff --git a/evmd/precompiles.go b/evmd/precompiles.go index 49fcdab1a..252e5de22 100644 --- a/evmd/precompiles.go +++ b/evmd/precompiles.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/evm/precompiles/bech32" cmn "github.com/cosmos/evm/precompiles/common" distprecompile "github.com/cosmos/evm/precompiles/distribution" + "github.com/cosmos/evm/precompiles/erc20factory" evidenceprecompile "github.com/cosmos/evm/precompiles/evidence" govprecompile "github.com/cosmos/evm/precompiles/gov" ics20precompile "github.com/cosmos/evm/precompiles/ics20" @@ -104,6 +105,11 @@ func NewAvailableStaticPrecompiles( panic(fmt.Errorf("failed to instantiate evidence precompile: %w", err)) } + erc20FactoryPrecompile, err := erc20factory.NewPrecompile(&erc20Keeper, bankKeeper) + if err != nil { + panic(fmt.Errorf("failed to instantiate erc20 factory precompile: %w", err)) + } + // Stateless precompiles precompiles[bech32Precompile.Address()] = bech32Precompile precompiles[p256Precompile.Address()] = p256Precompile @@ -116,6 +122,7 @@ func NewAvailableStaticPrecompiles( precompiles[govPrecompile.Address()] = govPrecompile precompiles[slashingPrecompile.Address()] = slashingPrecompile precompiles[evidencePrecompile.Address()] = evidencePrecompile + precompiles[erc20FactoryPrecompile.Address()] = erc20FactoryPrecompile return precompiles } diff --git a/local_node.sh b/local_node.sh index 2c3346403..5aaff475d 100755 --- a/local_node.sh +++ b/local_node.sh @@ -136,7 +136,7 @@ if [[ $overwrite == "y" || $overwrite == "Y" ]]; then jq '.app_state["bank"]["denom_metadata"]=[{"description":"The native staking token for evmd.","denom_units":[{"denom":"atest","exponent":0,"aliases":["attotest"]},{"denom":"test","exponent":18,"aliases":[]}],"base":"atest","display":"test","name":"Test Token","symbol":"TEST","uri":"","uri_hash":""}]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # Enable precompiles in EVM params - jq '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805"]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" + jq '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805", "0x0000000000000000000000000000000000000900"]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # Set EVM config jq '.app_state["evm"]["params"]["evm_denom"]="atest"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" diff --git a/precompiles/common/interfaces.go b/precompiles/common/interfaces.go index 631f91344..c1d8fb30e 100644 --- a/precompiles/common/interfaces.go +++ b/precompiles/common/interfaces.go @@ -16,4 +16,6 @@ type BankKeeper interface { GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error SpendableCoin(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin + MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error } diff --git a/precompiles/erc20factory/IERC20Factory.sol b/precompiles/erc20factory/IERC20Factory.sol new file mode 100644 index 000000000..1a2be905a --- /dev/null +++ b/precompiles/erc20factory/IERC20Factory.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.17; + +/** + * @dev The ERC20 Factory contract's address. + */ +address constant ERC20_FACTORY_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000900; + +/** + * @dev The ERC20 Factory contract's instance. + */ +IERC20Factory constant ERC20_FACTORY_CONTRACT = IERC20Factory(ERC20_FACTORY_PRECOMPILE_ADDRESS); + +interface IERC20Factory { + /** + * @dev Emitted when a new ERC20 token is created. + * @param tokenAddress The address of the ERC20 token. + * @param tokenPairType The type of token pair. + * @param salt The salt used for deployment. + * @param name The name of the token. + * @param symbol The symbol of the token. + * @param decimals The decimals of the token. + */ + event Create( + address indexed tokenAddress, + uint8 tokenPairType, + bytes32 salt, + string name, + string symbol, + uint8 decimals + ); + + /** + * @dev Defines a method for creating an ERC20 token. + * @param tokenPairType Token Pair type + * @param salt Salt used for deployment + * @param name The name of the token. + * @param symbol The symbol of the token. + * @param decimals the decimals of the token. + * @return tokenAddress The ERC20 token address. + */ + function create( + uint8 tokenPairType, + bytes32 salt, + string memory name, + string memory symbol, + uint8 decimals + ) external returns (address tokenAddress); + + /** + * @dev Calculates the deterministic address for a new token. + * @param tokenPairType Token Pair type + * @param salt Salt used for deployment + * @return tokenAddress The calculated ERC20 token address. + */ + function calculateAddress(uint8 tokenPairType, bytes32 salt) external view returns (address tokenAddress); +} \ No newline at end of file diff --git a/precompiles/erc20factory/abi.json b/precompiles/erc20factory/abi.json new file mode 100644 index 000000000..d3db7dc53 --- /dev/null +++ b/precompiles/erc20factory/abi.json @@ -0,0 +1,139 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "IERC20Factory", + "sourceName": "solidity/precompiles/erc20factory/IERC20Factory.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "uint8", + "name": "tokenPairType", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "calculateAddress", + "outputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "tokenPairType", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + }, + { + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "internalType": "uint256", + "name": "premintedSupply", + "type": "uint256" + } + ], + "name": "create", + "outputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "tokenPairType", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "premintedSupply", + "type": "uint256" + } + ], + "name": "Create", + "type": "event" + } + ], + "bytecode": "0x", + "deployedBytecode": "0x", + "linkReferences": {}, + "deployedLinkReferences": {} +} \ No newline at end of file diff --git a/precompiles/erc20factory/erc20factory.go b/precompiles/erc20factory/erc20factory.go new file mode 100644 index 000000000..0205e7a54 --- /dev/null +++ b/precompiles/erc20factory/erc20factory.go @@ -0,0 +1,155 @@ +package erc20factory + +import ( + "embed" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + + cmn "github.com/cosmos/evm/precompiles/common" + + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + Erc20FactoryAddress = "0x0000000000000000000000000000000000000900" + // GasCreate defines the gas required to create a new ERC20 Token Pair calculated from a ERC20 deploy transaction + GasCreate = 3_000_000 + // GasCalculateAddress defines the gas required to calculate the address of a new ERC20 Token Pair + GasCalculateAddress = 3_000 +) + +var _ vm.PrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +// Precompile defines the precompiled contract for Bech32 encoding. +type Precompile struct { + cmn.Precompile + erc20Keeper ERC20Keeper + bankKeeper BankKeeper +} + +// NewPrecompile creates a new bech32 Precompile instance as a +// PrecompiledContract interface. +func NewPrecompile(erc20Keeper ERC20Keeper, bankKeeper BankKeeper) (*Precompile, error) { + newABI, err := cmn.LoadABI(f, "abi.json") + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: cmn.Precompile{ + ABI: newABI, + KvGasConfig: storetypes.KVGasConfig(), + TransientKVGasConfig: storetypes.TransientGasConfig(), + }, + erc20Keeper: erc20Keeper, + bankKeeper: bankKeeper, + } + + // SetAddress defines the address of the distribution compile contract. + p.SetAddress(common.HexToAddress(Erc20FactoryAddress)) + return p, nil +} + +// Address defines the address of the bech32 precompiled contract. +func (Precompile) Address() common.Address { + return common.HexToAddress(Erc20FactoryAddress) +} + +// RequiredGas calculates the contract gas use. +func (p Precompile) RequiredGas(input []byte) uint64 { + // NOTE: This check avoid panicking when trying to decode the method ID + if len(input) < 4 { + return 0 + } + + methodID := input[:4] + method, err := p.MethodById(methodID) + if err != nil { + return 0 + } + + switch method.Name { + // ERC-20 transactions + case CreateMethod: + return GasCreate + case CalculateAddressMethod: + return GasCalculateAddress + default: + return 0 + } +} + +// Run executes the precompiled contract bech32 methods defined in the ABI. +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + // This handles any out of gas errors that may occur during the execution of a precompile query. + // It avoids panics and returns the out of gas error so the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + bz, err = p.HandleMethod(ctx, contract, stateDB, method, args) + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost, nil, tracing.GasChangeCallPrecompiledContract) { + return nil, vm.ErrOutOfGas + } + + // Process the native balance changes after the method execution. + err = p.GetBalanceHandler().AfterBalanceChange(ctx, stateDB) + if err != nil { + return nil, err + } + + return bz, nil +} + +// IsTransaction checks if the given method name corresponds to a transaction or query. +// +// Available ERC20 Factory transactions are: +// - Create +func (Precompile) IsTransaction(method *abi.Method) bool { + switch method.Name { + case CreateMethod: + return true + default: + return false + } +} + +// HandleMethod handles the execution of each of the ERC-20 Factory methods. +func (p *Precompile) HandleMethod( + ctx sdk.Context, + contract *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) (bz []byte, err error) { + switch method.Name { + // ERC-20 Factory transactions + case CreateMethod: + bz, err = p.Create(ctx, stateDB, method, contract.Caller(), args) + // ERC-20 Factory queries + case CalculateAddressMethod: + bz, err = p.CalculateAddress(method, contract.Caller(), args) + default: + return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) + } + return bz, err +} \ No newline at end of file diff --git a/precompiles/erc20factory/erc20factory_test.go b/precompiles/erc20factory/erc20factory_test.go new file mode 100644 index 000000000..6bb6c2865 --- /dev/null +++ b/precompiles/erc20factory/erc20factory_test.go @@ -0,0 +1,62 @@ +package erc20factory_test + +import ( + "math/big" + + "github.com/cosmos/evm/precompiles/erc20factory" + utiltx "github.com/cosmos/evm/testutil/tx" +) + +func (s *PrecompileTestSuite) TestIsTransaction() { + s.SetupTest() + + // Queries + method := s.precompile.Methods[erc20factory.CalculateAddressMethod] + s.Require().False(s.precompile.IsTransaction(&method)) + + // Transactions + method = s.precompile.Methods[erc20factory.CreateMethod] + s.Require().True(s.precompile.IsTransaction(&method)) +} + +func (s *PrecompileTestSuite) TestRequiredGas() { + s.SetupTest() + + mintAddr := utiltx.GenerateAddress() + decimals := uint8(18) + amount := big.NewInt(1000000) + name := "Test" + symbol := "TEST" + + testcases := []struct { + name string + malleate func() []byte + expGas uint64 + }{ + { + name: erc20factory.CalculateAddressMethod, + malleate: func() []byte { + bz, err := s.precompile.Pack(erc20factory.CalculateAddressMethod, uint8(0), [32]uint8{}) + s.Require().NoError(err, "expected no error packing ABI") + return bz + }, + expGas: erc20factory.GasCalculateAddress, + }, + { + name: erc20factory.CreateMethod, + malleate: func() []byte { + bz, err := s.precompile.Pack(erc20factory.CreateMethod, uint8(0), [32]uint8{}, name, symbol, decimals, mintAddr, amount) + s.Require().NoError(err, "expected no error packing ABI") + return bz + }, + expGas: erc20factory.GasCreate, + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + gas := s.precompile.RequiredGas(tc.malleate()) + s.Require().Equal(tc.expGas, gas) + }) + } +} \ No newline at end of file diff --git a/precompiles/erc20factory/events.go b/precompiles/erc20factory/events.go new file mode 100644 index 000000000..912189512 --- /dev/null +++ b/precompiles/erc20factory/events.go @@ -0,0 +1,57 @@ +package erc20factory + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + + cmn "github.com/cosmos/evm/precompiles/common" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // EventTypeCreate is the event type for the Create event. + EventTypeCreate = "Create" +) + +// EmitCreateEvent emits the Create event. +func (p Precompile) EmitCreateEvent(ctx sdk.Context, stateDB vm.StateDB, tokenAddress common.Address, tokenType uint8, salt [32]uint8, name string, symbol string, decimals uint8, minter common.Address, premintedSupply *big.Int) error { + event := p.Events[EventTypeCreate] + topics := make([]common.Hash, 2) // Only 2 topics: event ID + tokenAddress + + topics[0] = event.ID + + var err error + topics[1], err = cmn.MakeTopic(tokenAddress) + if err != nil { + return err + } + + // Pack the non-indexed event parameters into the data field + arguments := abi.Arguments{ + event.Inputs[1], // tokenType + event.Inputs[2], // salt + event.Inputs[3], // name + event.Inputs[4], // symbol + event.Inputs[5], // decimals + event.Inputs[6], // minter + event.Inputs[7], // premintedSupply + } + packed, err := arguments.Pack(tokenType, salt, name, symbol, decimals, minter, premintedSupply) + if err != nil { + return err + } + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: packed, + BlockNumber: uint64(ctx.BlockHeight()), //nolint:gosec // G115 // block height won't exceed uint64 + }) + + return nil +} \ No newline at end of file diff --git a/precompiles/erc20factory/events_test.go b/precompiles/erc20factory/events_test.go new file mode 100644 index 000000000..1420562d7 --- /dev/null +++ b/precompiles/erc20factory/events_test.go @@ -0,0 +1,70 @@ +package erc20factory_test + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + cmn "github.com/cosmos/evm/precompiles/common" + "github.com/cosmos/evm/precompiles/erc20factory" + utiltx "github.com/cosmos/evm/testutil/tx" +) + +func (s *PrecompileTestSuite) TestEmitCreateEvent() { + testcases := []struct { + testName string + tokenAddress common.Address + tokenType uint8 + salt [32]uint8 + name string + symbol string + decimals uint8 + minter common.Address + premintedSupply *big.Int + }{ + { + testName: "pass", + tokenAddress: utiltx.GenerateAddress(), + tokenType: 0, + salt: [32]uint8{0}, + name: "Test", + symbol: "TEST", + decimals: 18, + minter: utiltx.GenerateAddress(), + premintedSupply: big.NewInt(1000000), + }, + } + + for _, tc := range testcases { + s.Run(tc.testName, func() { + s.SetupTest() + stateDB := s.network.GetStateDB() + + err := s.precompile.EmitCreateEvent(s.network.GetContext(), stateDB, tc.tokenAddress, tc.tokenType, tc.salt, tc.name, tc.symbol, tc.decimals, tc.minter, tc.premintedSupply) + s.Require().NoError(err, "expected create event to be emitted successfully") + + log := stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[erc20factory.EventTypeCreate] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.network.GetContext().BlockHeight())) //nolint:gosec // G115 + + // Check event parameters + var createEvent erc20factory.EventCreate + err = cmn.UnpackLog(s.precompile.ABI, &createEvent, erc20factory.EventTypeCreate, *log) + s.Require().NoError(err, "unable to unpack log into create event") + + s.Require().Equal(tc.tokenAddress, createEvent.TokenAddress, "expected different token address") + s.Require().Equal(tc.tokenType, createEvent.TokenPairType, "expected different token type") + s.Require().Equal(tc.salt, createEvent.Salt, "expected different salt") + s.Require().Equal(tc.name, createEvent.Name, "expected different name") + s.Require().Equal(tc.symbol, createEvent.Symbol, "expected different symbol") + s.Require().Equal(tc.decimals, createEvent.Decimals, "expected different decimals") + s.Require().Equal(tc.minter, createEvent.Minter, "expected different minter") + s.Require().Equal(tc.premintedSupply, createEvent.PremintedSupply, "expected different preminted supply") + }) + } +} \ No newline at end of file diff --git a/precompiles/erc20factory/interfaces.go b/precompiles/erc20factory/interfaces.go new file mode 100644 index 000000000..920af5b41 --- /dev/null +++ b/precompiles/erc20factory/interfaces.go @@ -0,0 +1,25 @@ +package erc20factory + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + + erc20types "github.com/cosmos/evm/x/erc20/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +type ERC20Keeper interface { + SetToken(ctx sdk.Context, token erc20types.TokenPair) error + EnableDynamicPrecompile(ctx sdk.Context, address common.Address) error + IsDenomRegistered(ctx sdk.Context, denom string) bool +} + +type BankKeeper interface { + GetDenomMetaData(ctx context.Context, denom string) (banktypes.Metadata, bool) + SetDenomMetaData(ctx context.Context, denomMetaData banktypes.Metadata) + MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error +} \ No newline at end of file diff --git a/precompiles/erc20factory/query.go b/precompiles/erc20factory/query.go new file mode 100644 index 000000000..15dbcbf82 --- /dev/null +++ b/precompiles/erc20factory/query.go @@ -0,0 +1,29 @@ +package erc20factory + +import ( + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +const ( + // CalculateAddressMethod defines the ABI method name for the CalculateAddress + // query. + CalculateAddressMethod = "calculateAddress" +) + +// CalculateAddress calculates the address of a new ERC20 Token Pair +func (p Precompile) CalculateAddress( + method *abi.Method, + caller common.Address, + args []interface{}, +) ([]byte, error) { + tokenType, salt, err := ParseCalculateAddressArgs(args) + if err != nil { + return nil, err + } + + address := crypto.CreateAddress2(caller, salt, calculateCodeHash(tokenType)) + + return method.Outputs.Pack(address) +} \ No newline at end of file diff --git a/precompiles/erc20factory/query_test.go b/precompiles/erc20factory/query_test.go new file mode 100644 index 000000000..155af3a0c --- /dev/null +++ b/precompiles/erc20factory/query_test.go @@ -0,0 +1,75 @@ +package erc20factory_test + +import ( + "github.com/cosmos/evm/precompiles/erc20factory" + "github.com/ethereum/go-ethereum/common" +) + +func (s *PrecompileTestSuite) TestCalculateAddress() { + defaultCaller := common.HexToAddress("0xDc411BaFB148ebDA2B63EBD5f3D8669DD4383Af5") + + testcases := []struct { + name string + caller common.Address + args []interface{} + expPass bool + errContains string + expAddress common.Address + }{ + { + name: "pass - correct arguments", + caller: defaultCaller, + args: []interface{}{ + uint8(0), + [32]uint8(common.HexToHash("0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234").Bytes()), + }, + expPass: true, + expAddress: common.HexToAddress("0x188a919f3583f8e02183332E6c73E944E002C553"), + }, + { + name: "fail - invalid tokenType", + caller: defaultCaller, + args: []interface{}{ + "invalid tokenType", + "invalid salt", + }, + errContains: "invalid tokenType", + }, + { + name: "fail - invalid salt", + caller: defaultCaller, + args: []interface{}{ + uint8(0), + "invalid salt", + }, + errContains: "invalid salt", + }, + { + name: "fail - invalid number of arguments", + caller: defaultCaller, + args: []interface{}{ + 1, 2, 3, + }, + errContains: "invalid number of arguments", + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + s.SetupTest() + + precompile := s.setupERC20FactoryPrecompile() + + method := precompile.Methods[erc20factory.CalculateAddressMethod] + + bz, err := precompile.CalculateAddress( + &method, + tc.caller, + tc.args, + ) + + // NOTE: all output and error checking happens in here + s.requireOut(bz, err, method, tc.expPass, tc.errContains, tc.expAddress) + }) + } +} \ No newline at end of file diff --git a/precompiles/erc20factory/setup_test.go b/precompiles/erc20factory/setup_test.go new file mode 100644 index 000000000..f241dd75d --- /dev/null +++ b/precompiles/erc20factory/setup_test.go @@ -0,0 +1,61 @@ +package erc20factory_test + +import ( + "github.com/cosmos/evm/precompiles/erc20factory" + "github.com/cosmos/evm/testutil/integration/os/factory" + "github.com/cosmos/evm/testutil/integration/os/grpc" + testkeyring "github.com/cosmos/evm/testutil/integration/os/keyring" + "github.com/cosmos/evm/testutil/integration/os/network" + "github.com/stretchr/testify/suite" +) + +// PrecompileTestSuite is the implementation of the TestSuite interface for ERC20 Factory precompile +// unit tests. +type PrecompileTestSuite struct { + suite.Suite + + create network.UnitTestNetwork + options []network.ConfigOption + bondDenom string + network *network.UnitTestNetwork + factory factory.TxFactory + grpcHandler grpc.Handler + keyring testkeyring.Keyring + + precompile *erc20factory.Precompile +} + +func NewPrecompileTestSuite(create network.UnitTestNetwork, options ...network.ConfigOption) *PrecompileTestSuite { + return &PrecompileTestSuite{ + create: create, + options: options, + } +} + +func (s *PrecompileTestSuite) SetupTest() { + keyring := testkeyring.New(2) + options := []network.ConfigOption{ + network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), + } + options = append(options, s.options...) + integrationNetwork := network.NewUnitTestNetwork(options...) + grpcHandler := grpc.NewIntegrationHandler(integrationNetwork) + txFactory := factory.New(integrationNetwork, grpcHandler) + + ctx := integrationNetwork.GetContext() + sk := integrationNetwork.App.StakingKeeper + bondDenom, err := sk.BondDenom(ctx) + s.Require().NoError(err) + s.Require().NotEmpty(bondDenom, "bond denom cannot be empty") + + s.bondDenom = bondDenom + s.factory = txFactory + s.grpcHandler = grpcHandler + s.keyring = keyring + s.network = integrationNetwork + + // Instantiate the precompile with an exemplary token denomination. + // + // NOTE: This has to be done AFTER assigning the suite fields. + s.precompile = s.setupERC20FactoryPrecompile() +} diff --git a/precompiles/erc20factory/tx.go b/precompiles/erc20factory/tx.go new file mode 100644 index 000000000..a2fc8a00b --- /dev/null +++ b/precompiles/erc20factory/tx.go @@ -0,0 +1,140 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package erc20factory + +import ( + "encoding/binary" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + + erc20types "github.com/cosmos/evm/x/erc20/types" + + "cosmossdk.io/errors" + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +const ( + // CreateMethod defines the ABI method name to create a new ERC20 Token Pair + CreateMethod = "create" +) + +// Create CreateERC20Precompile creates a new ERC20 TokenPair +func (p Precompile) Create( + ctx sdk.Context, + stateDB vm.StateDB, + method *abi.Method, + caller common.Address, + args []interface{}, +) ([]byte, error) { + tokenType, salt, name, symbol, decimals, minter, premintedSupply, err := ParseCreateArgs(args) + if err != nil { + return nil, err + } + + address := crypto.CreateAddress2(caller, salt, calculateCodeHash(tokenType)) + + metadata, err := p.createCoinMetadata(ctx, address, name, symbol, decimals) + if err != nil { + return nil, errors.Wrap( + err, "failed to create wrapped coin denom metadata for ERC20", + ) + } + + if err := metadata.Validate(); err != nil { + return nil, errors.Wrapf( + err, "ERC20 token data is invalid for contract %s", address.String(), + ) + } + + p.bankKeeper.SetDenomMetaData(ctx, *metadata) + + pair := erc20types.NewTokenPair(address, metadata.Name, erc20types.OWNER_EXTERNAL) + + p.erc20Keeper.SetToken(ctx, pair) + + err = p.erc20Keeper.EnableDynamicPrecompile(ctx, pair.GetERC20Contract()) + if err != nil { + return nil, err + } + + coins := sdk.NewCoins(sdk.NewCoin(metadata.Base, math.NewIntFromBigInt(premintedSupply))) + if err := p.bankKeeper.MintCoins(ctx, erc20types.ModuleName, coins); err != nil { + return nil, err + } + if err := p.bankKeeper.SendCoinsFromModuleToAccount(ctx, erc20types.ModuleName, sdk.AccAddress(minter.Bytes()), coins); err != nil { + return nil, err + } + + if err = p.EmitCreateEvent(ctx, stateDB, address, tokenType, salt, name, symbol, decimals, minter, premintedSupply); err != nil { + return nil, err + } + + return method.Outputs.Pack(address) +} + +func (p Precompile) createCoinMetadata(ctx sdk.Context, address common.Address, name string, symbol string, decimals uint8) (*banktypes.Metadata, error) { + addressString := address.String() + denom := erc20types.CreateDenom(addressString) + + _, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if found { + return nil, errors.Wrap( + erc20types.ErrInternalTokenPair, "denom metadata already registered", + ) + } + + if p.erc20Keeper.IsDenomRegistered(ctx, denom) { + return nil, errors.Wrapf( + erc20types.ErrInternalTokenPair, "coin denomination already registered: %s", name, + ) + } + + // base denomination + base := erc20types.CreateDenom(addressString) + + // create a bank denom metadata based on the ERC20 token ABI details + // metadata name is should always be the contract since it's the key + // to the bank store + metadata := banktypes.Metadata{ + Description: erc20types.CreateDenomDescription(addressString), + Base: base, + // NOTE: Denom units MUST be increasing + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: base, + Exponent: 0, + }, + }, + Name: base, + Symbol: symbol, + Display: base, + } + + // only append metadata if decimals > 0, otherwise validation fails + if decimals > 0 { + nameSanitized := erc20types.SanitizeERC20Name(name) + metadata.DenomUnits = append( + metadata.DenomUnits, + &banktypes.DenomUnit{ + Denom: nameSanitized, + Exponent: uint32(decimals), //#nosec G115 + }, + ) + metadata.Display = nameSanitized + } + + return &metadata, nil +} + +func calculateCodeHash(tokenType uint8) []byte { + tokenTypeBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(tokenTypeBytes, uint32(tokenType)) + return tokenTypeBytes +} diff --git a/precompiles/erc20factory/tx_test.go b/precompiles/erc20factory/tx_test.go new file mode 100644 index 000000000..21b46998b --- /dev/null +++ b/precompiles/erc20factory/tx_test.go @@ -0,0 +1,172 @@ +package erc20factory_test + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + + "github.com/cosmos/evm/precompiles/erc20factory" + erc20types "github.com/cosmos/evm/x/erc20/types" +) + +func (s *PrecompileTestSuite) TestCreate() { + caller := common.HexToAddress("0x2c7882f69Cd115F470aAEde121f57F932936a56f") + mintAddr := common.HexToAddress("0x73657398D483143AF7db7899757e5E7037fB713d") + expectedAddress := common.HexToAddress("0x30E56567F73403eD713dA0b0419e4A5330A16896") + amount := big.NewInt(1000000) + decimals := uint8(18) + name := "Test" + symbol := "TEST" + + method := s.precompile.Methods[erc20factory.CreateMethod] + + testcases := []struct { + name string + args []interface{} + expPass bool + postExpPass func(output []byte) + errContains string + expAddress common.Address + }{ + { + name: "pass - correct arguments", + args: []interface{}{uint8(0), [32]uint8(common.HexToHash("0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234").Bytes()), name, symbol, decimals, mintAddr, amount}, + expPass: true, + postExpPass: func(output []byte) { + res, err := method.Outputs.Unpack(output) + s.Require().NoError(err, "expected no error unpacking output") + s.Require().Len(res, 1, "expected one output") + address, ok := res[0].(common.Address) + s.Require().True(ok, "expected address type") + + // Check the balance of the token for the mintAddr + balance := s.network.App.BankKeeper.GetBalance(s.network.GetContext(), sdk.AccAddress(mintAddr.Bytes()), erc20types.CreateDenom(address.String())) + s.Require().Equal(amount, balance.Amount.BigInt(), "expected balance to match preminted amount") + + s.Require().Equal(address.String(), expectedAddress, "expected address to match") + + }, + expAddress: expectedAddress, + }, + { + name: "fail - invalid tokenType", + args: []interface{}{ + "invalid tokenType", + [32]uint8{}, + name, + symbol, + decimals, + mintAddr, + amount, + }, + errContains: "invalid tokenType", + }, + { + name: "fail - invalid salt", + args: []interface{}{ + uint8(0), + "invalid salt", + name, + symbol, + decimals, + mintAddr, + amount, + }, + errContains: "invalid salt", + }, + { + name: "fail - invalid name", + args: []interface{}{ + uint8(0), + [32]uint8{}, + "", + symbol, + decimals, + mintAddr, + amount, + }, + errContains: "invalid name", + }, + { + name: "fail - invalid symbol", + args: []interface{}{ + uint8(0), + [32]uint8{}, + name, + "is", + decimals, + mintAddr, + amount, + }, + errContains: "invalid symbol", + }, + { + name: "fail - invalid decimals", + args: []interface{}{ + uint8(0), + [32]uint8{}, + name, + symbol, + "invalid decimals", + mintAddr, + amount, + }, + errContains: "invalid decimals", + }, + { + name: "fail - invalid minter", + args: []interface{}{ + uint8(0), + [32]uint8{}, + name, + symbol, + decimals, + "invalid address", + amount, + }, + errContains: "invalid minter", + }, + { + name: "fail - invalid preminted supply", + args: []interface{}{ + uint8(0), + [32]uint8{}, + name, + symbol, + decimals, + mintAddr, + "invalid amount", + }, + errContains: "invalid premintedSupply", + }, + { + name: "fail - invalid number of arguments", + args: []interface{}{ + 1, 2, 3, + }, + errContains: "invalid number of arguments", + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + s.SetupTest() + + precompile := s.setupERC20FactoryPrecompile() + + method := precompile.Methods[erc20factory.CreateMethod] + + bz, err := precompile.Create( + s.network.GetContext(), + s.network.GetStateDB(), + &method, + caller, + tc.args, + ) + + // NOTE: all output and error checking happens in here + s.requireOut(bz, err, method, tc.expPass, tc.errContains, tc.expAddress) + }) + } +} diff --git a/precompiles/erc20factory/types.go b/precompiles/erc20factory/types.go new file mode 100644 index 000000000..fa186d342 --- /dev/null +++ b/precompiles/erc20factory/types.go @@ -0,0 +1,101 @@ +package erc20factory + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + + cmn "github.com/cosmos/evm/precompiles/common" +) + +// EventCreate defines the event data for the ERC20 Factory Create event. +type EventCreate struct { + TokenAddress common.Address + TokenPairType uint8 + Salt [32]uint8 + Name string + Symbol string + Decimals uint8 + Minter common.Address + PremintedSupply *big.Int +} + +// ParseCreateArgs parses the arguments from the create method and returns +// the token type, salt, name, symbol, decimals, minter, and preminted supply. +func ParseCreateArgs(args []interface{}) (tokenType uint8, salt [32]uint8, name string, symbol string, decimals uint8, minter common.Address, premintedSupply *big.Int, err error) { + if len(args) != 7 { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 7, len(args)) + } + + tokenType, ok := args[0].(uint8) + if !ok { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid tokenType") + } + + salt, ok = args[1].([32]uint8) + if !ok { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid salt") + } + + name, ok = args[2].(string) + if !ok || len(name) < 3 || len(name) > 128 { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid name") + } + + symbol, ok = args[3].(string) + if !ok || len(symbol) < 3 || len(symbol) > 16 { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid symbol") + } + + decimals, ok = args[4].(uint8) + if !ok { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid decimals") + } + + minter, ok = args[5].(common.Address) + if !ok { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid minter") + } + + // Validate that minter is not the zero address + if minter == (common.Address{}) { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid minter: cannot be zero address") + } + + premintedSupply, ok = args[6].(*big.Int) + if !ok { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid premintedSupply: expected *big.Int") + } + + if premintedSupply.Sign() < 0 { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid premintedSupply: cannot be negative") + } + + maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) + if premintedSupply.Cmp(maxUint256) > 0 { + return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("premintedSupply exceeds uint256 maximum") + } + + return tokenType, salt, name, symbol, decimals, minter, premintedSupply, nil +} + +// ParseCalculateAddressArgs parses the arguments from the calculateAddress method and returns +// the token type and salt. +func ParseCalculateAddressArgs(args []interface{}) (tokenType uint8, salt [32]uint8, err error) { + if len(args) != 2 { + return uint8(0), [32]uint8{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 2, len(args)) + } + + tokenType, ok := args[0].(uint8) + if !ok { + return uint8(0), [32]uint8{}, fmt.Errorf("invalid tokenType") + } + + salt, ok = args[1].([32]uint8) + if !ok { + return uint8(0), [32]uint8{}, fmt.Errorf("invalid salt") + } + + return tokenType, salt, nil +} \ No newline at end of file diff --git a/precompiles/erc20factory/types_test.go b/precompiles/erc20factory/types_test.go new file mode 100644 index 000000000..9c0d458df --- /dev/null +++ b/precompiles/erc20factory/types_test.go @@ -0,0 +1,223 @@ +package erc20factory_test + +import ( + "math/big" + + "github.com/cosmos/evm/precompiles/erc20factory" + utiltx "github.com/cosmos/evm/testutil/tx" + "github.com/ethereum/go-ethereum/common" +) + +func (s *PrecompileTestSuite) TestParseCalculateAddressArgs() { + s.SetupTest() + + testcases := []struct { + name string + args []interface{} + expPass bool + errContains string + }{ + { + name: "pass - correct arguments", + args: []interface{}{ + uint8(0), + [32]uint8{}, + }, + expPass: true, + }, + { + name: "fail - invalid tokenType", + args: []interface{}{ + "invalid tokenType", + [32]uint8{}, + }, + errContains: "invalid tokenType", + }, + { + name: "fail - invalid salt", + args: []interface{}{ + uint8(0), + "invalid salt", + }, + }, + { + name: "fail - invalid number of arguments", + args: []interface{}{ + 1, 2, 3, + }, + errContains: "invalid number of arguments", + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + tokenType, salt, err := erc20factory.ParseCalculateAddressArgs(tc.args) + if tc.expPass { + s.Require().NoError(err, "unexpected error parsing the calculate address arguments") + s.Require().Equal(tokenType, tc.args[0], "expected different token type") + s.Require().Equal(salt, tc.args[1], "expected different salt") + } else { + s.Require().Error(err, "expected an error parsing the calculate address arguments") + s.Require().ErrorContains(err, tc.errContains, "expected different error message") + } + }) + } +} + +func (s *PrecompileTestSuite) TestParseCreateArgs() { + addr := utiltx.GenerateAddress() + decimals := uint8(18) + amount := big.NewInt(1000000) + name := "Test" + symbol := "TEST" + + s.SetupTest() + + testcases := []struct { + name string + args []interface{} + expPass bool + errContains string + }{ + { + name: "pass - correct arguments", + args: []interface{}{ + uint8(0), + [32]uint8{}, + name, + symbol, + decimals, + addr, + amount, + }, + expPass: true, + }, + { + name: "fail - invalid tokenType", + args: []interface{}{ + "invalid tokenType", + [32]uint8{}, + name, + symbol, + decimals, + addr, + big.NewInt(1000000), + }, + }, + { + name: "fail - invalid salt", + args: []interface{}{ + uint8(0), + "invalid salt", + name, + symbol, + decimals, + addr, + big.NewInt(1000000), + }, + }, + { + name: "fail - invalid name", + args: []interface{}{ + uint8(0), + [32]uint8{}, + uint8(0), + symbol, + decimals, + addr, + big.NewInt(1000000), + }, + errContains: "invalid name", + }, + { + name: "fail - invalid symbol", + args: []interface{}{ + uint8(0), + [32]uint8{}, + name, + "is", + decimals, + addr, + big.NewInt(1000000), + }, + errContains: "invalid symbol", + }, + { + name: "fail - invalid decimals", + args: []interface{}{ + uint8(0), + [32]uint8{}, + name, + symbol, + "invalid decimals", + addr, + big.NewInt(1000000), + }, + errContains: "invalid decimals", + }, + { + name: "fail - invalid minter", + args: []interface{}{ + uint8(0), + [32]uint8{}, + name, + symbol, + decimals, + "invalid address", + big.NewInt(1000000), + }, + errContains: "invalid minter", + }, + { + name: "fail - zero address minter", + args: []interface{}{ + uint8(0), + [32]uint8{}, + name, + symbol, + decimals, + common.Address{}, // Zero address + big.NewInt(1000000), + }, + errContains: "invalid minter: cannot be zero address", + }, + { + name: "fail - invalid preminted supply", + args: []interface{}{ + uint8(0), + [32]uint8{}, + name, + symbol, + decimals, + addr, + big.NewInt(-1), + }, + errContains: "invalid premintedSupply: cannot be negative", + }, + { + name: "fail - invalid number of arguments", + args: []interface{}{ + 1, 2, 3, 4, 5, + }, + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + tokenType, salt, name, symbol, decimals, minter, premintedSupply, err := erc20factory.ParseCreateArgs(tc.args) + if tc.expPass { + s.Require().NoError(err, "unexpected error parsing the create arguments") + s.Require().Equal(tokenType, tc.args[0], "expected different token type") + s.Require().Equal(salt, tc.args[1], "expected different salt") + s.Require().Equal(name, tc.args[2], "expected different name") + s.Require().Equal(symbol, tc.args[3], "expected different symbol") + s.Require().Equal(decimals, tc.args[4], "expected different decimals") + s.Require().Equal(minter, tc.args[5], "expected different minter") + s.Require().Equal(premintedSupply, tc.args[6], "expected different preminted supply") + } else { + s.Require().Error(err, "expected an error parsing the create arguments") + s.Require().ErrorContains(err, tc.errContains, "expected different error message") + } + }) + } +} \ No newline at end of file diff --git a/precompiles/erc20factory/utils_test.go b/precompiles/erc20factory/utils_test.go new file mode 100644 index 000000000..c38f68273 --- /dev/null +++ b/precompiles/erc20factory/utils_test.go @@ -0,0 +1,56 @@ +package erc20factory_test + +import ( + "math/big" + + "github.com/cosmos/evm/precompiles/erc20factory" + "github.com/ethereum/go-ethereum/accounts/abi" +) + +func (s *PrecompileTestSuite) setupERC20FactoryPrecompile() *erc20factory.Precompile { + precompile, err := erc20factory.NewPrecompile( + &s.network.App.Erc20Keeper, + s.network.App.BankKeeper) + s.Require().NoError(err, "failed to create erc20factory precompile") + + return precompile +} + +// requireOut is a helper utility to reduce the amount of boilerplate code in the query tests. +// +// It requires the output bytes and error to match the expected values. Additionally, the method outputs +// are unpacked and the first value is compared to the expected value. +// +// NOTE: It's sufficient to only check the first value because all methods in the ERC20 precompile only +// return a single value. +func (s *PrecompileTestSuite) requireOut( + bz []byte, + err error, + method abi.Method, + expPass bool, + errContains string, + expValue interface{}, +) { + if expPass { + s.Require().NoError(err, "expected no error") + s.Require().NotEmpty(bz, "expected bytes not to be empty") + + // Unpack the name into a string + out, err := method.Outputs.Unpack(bz) + s.Require().NoError(err, "expected no error unpacking") + + // Check if expValue is a big.Int. Because of a difference in uninitialized/empty values for big.Ints, + // this comparison is often not working as expected, so we convert to Int64 here and compare those values. + bigExp, ok := expValue.(*big.Int) + if ok { + bigOut, ok := out[0].(*big.Int) + s.Require().True(ok, "expected output to be a big.Int") + s.Require().Equal(bigExp.Int64(), bigOut.Int64(), "expected different value") + } else { + s.Require().Equal(expValue, out[0], "expected different value") + } + } else { + s.Require().Error(err, "expected error") + s.Require().Contains(err.Error(), errContains, "expected different error") + } +} \ No newline at end of file diff --git a/tests/solidity/init-node.sh b/tests/solidity/init-node.sh index 152f94276..09ba72aa9 100755 --- a/tests/solidity/init-node.sh +++ b/tests/solidity/init-node.sh @@ -76,7 +76,7 @@ jq '.app_state["evm"]["params"]["evm_denom"]="atest"' "$GENESIS" >"$TMP_GENESIS" jq '.app_state["mint"]["params"]["mint_denom"]="atest"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # Enable precompiles in EVM params -jq '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804"]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" +jq '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804", "0x0000000000000000000000000000000000000900"]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # Change proposal periods to pass within a reasonable time for local testing sed -i.bak 's/"max_deposit_period": "172800s"/"max_deposit_period": "30s"/g' "$GENESIS" diff --git a/tests/solidity/suites/precompiles/test/erc20factory.js b/tests/solidity/suites/precompiles/test/erc20factory.js new file mode 100644 index 000000000..079c18fb5 --- /dev/null +++ b/tests/solidity/suites/precompiles/test/erc20factory.js @@ -0,0 +1,62 @@ +const { expect } = require('chai') +const hre = require('hardhat') + +const abi = [ + "function create(uint8 tokenPairType, bytes32 salt, string memory name, string memory symbol, uint8 decimals, address minter, uint256 premintedSupply) external returns (address)", + "function calculateAddress(uint8 tokenPairType, bytes32 salt) external view returns (address)", + "event Create(address indexed tokenAddress, uint8 tokenPairType, bytes32 salt, string name, string symbol, uint8 decimals, address minter, uint256 premintedSupply)" +] + +describe('ERC20Factory', function () { + + it('should calculate the correct address', async function () { + const salt = '0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234' + const tokenPairType = 0 + const erc20Factory = await hre.ethers.getContractAt('IERC20Factory', '0x0000000000000000000000000000000000000900') + const expectedAddress = await erc20Factory.calculateAddress(tokenPairType, salt) + expect(expectedAddress).to.equal('0x6a040655fE545126cD341506fCD4571dB3A444F9') + }) + + it('should create a new ERC20 token', async function () { + const salt = '0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234' + const name = 'Test' + const symbol = 'TEST' + const decimals = 18 + const tokenPairType = 0 + const premintedSupply = hre.ethers.parseEther("1000000") // 1M tokens + + const [signer] = await hre.ethers.getSigners() + const minter = signer.address + + // Calculate the expected token address before deployment + const erc20Factory = await hre.ethers.getContractAt('IERC20Factory', '0x0000000000000000000000000000000000000900') + + const tokenAddress = await erc20Factory.calculateAddress(tokenPairType, salt) + const tx = await erc20Factory.connect(signer).create(tokenPairType, salt, name, symbol, decimals, minter, premintedSupply) + + // Get the token address from the transaction receipt + const receipt = await tx.wait() + expect(receipt.status).to.equal(1) // Check transaction was successful + + // Create a contract instance with the full ABI including events for event filtering + const erc20FactoryWithEvents = new hre.ethers.Contract('0x0000000000000000000000000000000000000900', abi, signer) + + // Get the Create event from the transaction receipt + const createEvents = await erc20FactoryWithEvents.queryFilter(erc20FactoryWithEvents.filters.Create(), receipt.blockNumber, receipt.blockNumber) + expect(createEvents.length).to.equal(1) + expect(createEvents[0].args.tokenAddress).to.equal(tokenAddress) + expect(createEvents[0].args.tokenPairType).to.equal(tokenPairType) + expect(createEvents[0].args.salt).to.equal(salt) + expect(createEvents[0].args.name).to.equal(name) + expect(createEvents[0].args.symbol).to.equal(symbol) + expect(createEvents[0].args.decimals).to.equal(decimals) + expect(createEvents[0].args.minter).to.equal(minter) + expect(createEvents[0].args.premintedSupply).to.equal(premintedSupply) + + // Get the token contract instance + const erc20Token = await hre.ethers.getContractAt('contracts/cosmos/erc20/IERC20.sol:IERC20', tokenAddress) + + // Verify token details through IERC20 queries + expect(await erc20Token.totalSupply()).to.equal(premintedSupply) + }) +}) \ No newline at end of file diff --git a/x/precisebank/keeper/keeper.go b/x/precisebank/keeper/keeper.go index 1185ea518..7bbd1b840 100644 --- a/x/precisebank/keeper/keeper.go +++ b/x/precisebank/keeper/keeper.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) // Enforce that Keeper implements the expected keeper interfaces @@ -48,3 +49,11 @@ func (k Keeper) IterateTotalSupply(ctx context.Context, cb func(coin sdk.Coin) b func (k Keeper) GetSupply(ctx context.Context, denom string) sdk.Coin { return k.bk.GetSupply(ctx, denom) } + +func (k Keeper) GetDenomMetaData(ctx context.Context, denom string) (banktypes.Metadata, bool) { + return k.bk.GetDenomMetaData(ctx, denom) +} + +func (k Keeper) SetDenomMetaData(ctx context.Context, denomMetaData banktypes.Metadata) { + k.bk.SetDenomMetaData(ctx, denomMetaData) +} \ No newline at end of file From 3c5808fab4a7423118cb4f8defdb1cd6cfd10dd3 Mon Sep 17 00:00:00 2001 From: GuillemGarciaDev Date: Tue, 5 Aug 2025 15:09:12 +0200 Subject: [PATCH 2/8] refactor(erc20factory): integration tests --- .../erc20factory/test_erc20factory.go | 2 +- .../precompiles/erc20factory/test_events.go | 2 +- .../precompiles/erc20factory/test_query.go | 2 +- .../precompiles/erc20factory/test_setup.go | 18 +++++++++--------- .../precompiles/erc20factory/test_tx.go | 4 ++-- .../precompiles/erc20factory/test_types.go | 2 +- .../precompiles/erc20factory/test_utils.go | 6 +++--- 7 files changed, 18 insertions(+), 18 deletions(-) rename precompiles/erc20factory/erc20factory_test.go => tests/integration/precompiles/erc20factory/test_erc20factory.go (98%) rename precompiles/erc20factory/events_test.go => tests/integration/precompiles/erc20factory/test_events.go (99%) rename precompiles/erc20factory/query_test.go => tests/integration/precompiles/erc20factory/test_query.go (98%) rename precompiles/erc20factory/setup_test.go => tests/integration/precompiles/erc20factory/test_setup.go (72%) rename precompiles/erc20factory/tx_test.go => tests/integration/precompiles/erc20factory/test_tx.go (95%) rename precompiles/erc20factory/types_test.go => tests/integration/precompiles/erc20factory/test_types.go (99%) rename precompiles/erc20factory/utils_test.go => tests/integration/precompiles/erc20factory/test_utils.go (95%) diff --git a/precompiles/erc20factory/erc20factory_test.go b/tests/integration/precompiles/erc20factory/test_erc20factory.go similarity index 98% rename from precompiles/erc20factory/erc20factory_test.go rename to tests/integration/precompiles/erc20factory/test_erc20factory.go index 6bb6c2865..00c02a078 100644 --- a/precompiles/erc20factory/erc20factory_test.go +++ b/tests/integration/precompiles/erc20factory/test_erc20factory.go @@ -1,4 +1,4 @@ -package erc20factory_test +package erc20factory import ( "math/big" diff --git a/precompiles/erc20factory/events_test.go b/tests/integration/precompiles/erc20factory/test_events.go similarity index 99% rename from precompiles/erc20factory/events_test.go rename to tests/integration/precompiles/erc20factory/test_events.go index 1420562d7..2ecddec34 100644 --- a/precompiles/erc20factory/events_test.go +++ b/tests/integration/precompiles/erc20factory/test_events.go @@ -1,4 +1,4 @@ -package erc20factory_test +package erc20factory import ( "math/big" diff --git a/precompiles/erc20factory/query_test.go b/tests/integration/precompiles/erc20factory/test_query.go similarity index 98% rename from precompiles/erc20factory/query_test.go rename to tests/integration/precompiles/erc20factory/test_query.go index 155af3a0c..0e376deeb 100644 --- a/precompiles/erc20factory/query_test.go +++ b/tests/integration/precompiles/erc20factory/test_query.go @@ -1,4 +1,4 @@ -package erc20factory_test +package erc20factory import ( "github.com/cosmos/evm/precompiles/erc20factory" diff --git a/precompiles/erc20factory/setup_test.go b/tests/integration/precompiles/erc20factory/test_setup.go similarity index 72% rename from precompiles/erc20factory/setup_test.go rename to tests/integration/precompiles/erc20factory/test_setup.go index f241dd75d..b0d25ccb8 100644 --- a/precompiles/erc20factory/setup_test.go +++ b/tests/integration/precompiles/erc20factory/test_setup.go @@ -1,11 +1,11 @@ -package erc20factory_test +package erc20factory import ( "github.com/cosmos/evm/precompiles/erc20factory" - "github.com/cosmos/evm/testutil/integration/os/factory" - "github.com/cosmos/evm/testutil/integration/os/grpc" - testkeyring "github.com/cosmos/evm/testutil/integration/os/keyring" - "github.com/cosmos/evm/testutil/integration/os/network" + "github.com/cosmos/evm/testutil/integration/evm/factory" + "github.com/cosmos/evm/testutil/integration/evm/grpc" + "github.com/cosmos/evm/testutil/integration/evm/network" + testkeyring "github.com/cosmos/evm/testutil/keyring" "github.com/stretchr/testify/suite" ) @@ -14,7 +14,7 @@ import ( type PrecompileTestSuite struct { suite.Suite - create network.UnitTestNetwork + create network.CreateEvmApp options []network.ConfigOption bondDenom string network *network.UnitTestNetwork @@ -25,7 +25,7 @@ type PrecompileTestSuite struct { precompile *erc20factory.Precompile } -func NewPrecompileTestSuite(create network.UnitTestNetwork, options ...network.ConfigOption) *PrecompileTestSuite { +func NewPrecompileTestSuite(create network.CreateEvmApp, options ...network.ConfigOption) *PrecompileTestSuite { return &PrecompileTestSuite{ create: create, options: options, @@ -38,12 +38,12 @@ func (s *PrecompileTestSuite) SetupTest() { network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), } options = append(options, s.options...) - integrationNetwork := network.NewUnitTestNetwork(options...) + integrationNetwork := network.NewUnitTestNetwork(s.create, options...) grpcHandler := grpc.NewIntegrationHandler(integrationNetwork) txFactory := factory.New(integrationNetwork, grpcHandler) ctx := integrationNetwork.GetContext() - sk := integrationNetwork.App.StakingKeeper + sk := integrationNetwork.App.GetStakingKeeper() bondDenom, err := sk.BondDenom(ctx) s.Require().NoError(err) s.Require().NotEmpty(bondDenom, "bond denom cannot be empty") diff --git a/precompiles/erc20factory/tx_test.go b/tests/integration/precompiles/erc20factory/test_tx.go similarity index 95% rename from precompiles/erc20factory/tx_test.go rename to tests/integration/precompiles/erc20factory/test_tx.go index 21b46998b..562d1f808 100644 --- a/precompiles/erc20factory/tx_test.go +++ b/tests/integration/precompiles/erc20factory/test_tx.go @@ -1,4 +1,4 @@ -package erc20factory_test +package erc20factory import ( "math/big" @@ -41,7 +41,7 @@ func (s *PrecompileTestSuite) TestCreate() { s.Require().True(ok, "expected address type") // Check the balance of the token for the mintAddr - balance := s.network.App.BankKeeper.GetBalance(s.network.GetContext(), sdk.AccAddress(mintAddr.Bytes()), erc20types.CreateDenom(address.String())) + balance := s.network.App.GetBankKeeper().GetBalance(s.network.GetContext(), sdk.AccAddress(mintAddr.Bytes()), erc20types.CreateDenom(address.String())) s.Require().Equal(amount, balance.Amount.BigInt(), "expected balance to match preminted amount") s.Require().Equal(address.String(), expectedAddress, "expected address to match") diff --git a/precompiles/erc20factory/types_test.go b/tests/integration/precompiles/erc20factory/test_types.go similarity index 99% rename from precompiles/erc20factory/types_test.go rename to tests/integration/precompiles/erc20factory/test_types.go index 9c0d458df..470ef0f61 100644 --- a/precompiles/erc20factory/types_test.go +++ b/tests/integration/precompiles/erc20factory/test_types.go @@ -1,4 +1,4 @@ -package erc20factory_test +package erc20factory import ( "math/big" diff --git a/precompiles/erc20factory/utils_test.go b/tests/integration/precompiles/erc20factory/test_utils.go similarity index 95% rename from precompiles/erc20factory/utils_test.go rename to tests/integration/precompiles/erc20factory/test_utils.go index c38f68273..fc1381a67 100644 --- a/precompiles/erc20factory/utils_test.go +++ b/tests/integration/precompiles/erc20factory/test_utils.go @@ -1,4 +1,4 @@ -package erc20factory_test +package erc20factory import ( "math/big" @@ -9,8 +9,8 @@ import ( func (s *PrecompileTestSuite) setupERC20FactoryPrecompile() *erc20factory.Precompile { precompile, err := erc20factory.NewPrecompile( - &s.network.App.Erc20Keeper, - s.network.App.BankKeeper) + s.network.App.GetErc20Keeper(), + s.network.App.GetBankKeeper()) s.Require().NoError(err, "failed to create erc20factory precompile") return precompile From 08670ca76ca15acbaa685ab1eebb3fc3479c225f Mon Sep 17 00:00:00 2001 From: GuillemGarciaDev Date: Tue, 5 Aug 2025 15:12:20 +0200 Subject: [PATCH 3/8] fix(precisebank): remove already declared methods --- x/precisebank/keeper/keeper.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/x/precisebank/keeper/keeper.go b/x/precisebank/keeper/keeper.go index 7bbd1b840..1185ea518 100644 --- a/x/precisebank/keeper/keeper.go +++ b/x/precisebank/keeper/keeper.go @@ -10,7 +10,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) // Enforce that Keeper implements the expected keeper interfaces @@ -49,11 +48,3 @@ func (k Keeper) IterateTotalSupply(ctx context.Context, cb func(coin sdk.Coin) b func (k Keeper) GetSupply(ctx context.Context, denom string) sdk.Coin { return k.bk.GetSupply(ctx, denom) } - -func (k Keeper) GetDenomMetaData(ctx context.Context, denom string) (banktypes.Metadata, bool) { - return k.bk.GetDenomMetaData(ctx, denom) -} - -func (k Keeper) SetDenomMetaData(ctx context.Context, denomMetaData banktypes.Metadata) { - k.bk.SetDenomMetaData(ctx, denomMetaData) -} \ No newline at end of file From 0625e33b0ac0a3f07dfb4430f2bd1c3d744e9b3d Mon Sep 17 00:00:00 2001 From: GuillemGarciaDev Date: Tue, 19 Aug 2025 09:56:17 +0200 Subject: [PATCH 4/8] fix: update branch with main changes --- .../tests/integration/mempool/mempool_test.go | 3 +- go.mod | 5 +++ go.sum | 10 +++++ local_node.sh | 2 +- precompiles/common/mocks/BankKeeper.go | 39 ++++++++++++++++++- precompiles/erc20factory/erc20factory.go | 2 +- precompiles/erc20factory/events.go | 2 +- precompiles/erc20factory/interfaces.go | 2 +- precompiles/erc20factory/query.go | 2 +- precompiles/erc20factory/tx.go | 5 ++- precompiles/erc20factory/types.go | 2 +- .../erc20factory/test_erc20factory.go | 11 ++++-- .../precompiles/erc20factory/test_events.go | 2 +- .../precompiles/erc20factory/test_query.go | 5 ++- .../precompiles/erc20factory/test_setup.go | 3 +- .../precompiles/erc20factory/test_tx.go | 4 +- .../precompiles/erc20factory/test_types.go | 5 ++- .../precompiles/erc20factory/test_utils.go | 5 ++- tests/jsonrpc/simulator/namespaces/eth.go | 5 --- tests/jsonrpc/simulator/report/report.go | 1 - tests/jsonrpc/simulator/types/context.go | 2 - tests/jsonrpc/simulator/utils/test_helpers.go | 1 - .../suites/precompiles/test/erc20factory.js | 4 +- 23 files changed, 90 insertions(+), 32 deletions(-) diff --git a/evmd/tests/integration/mempool/mempool_test.go b/evmd/tests/integration/mempool/mempool_test.go index 3714c9afc..8c3830dba 100644 --- a/evmd/tests/integration/mempool/mempool_test.go +++ b/evmd/tests/integration/mempool/mempool_test.go @@ -1,9 +1,10 @@ package mempool import ( - "github.com/cosmos/evm/evmd/tests/integration" "testing" + "github.com/cosmos/evm/evmd/tests/integration" + "github.com/stretchr/testify/suite" "github.com/cosmos/evm/tests/integration/mempool" diff --git a/go.mod b/go.mod index 7bbd55c08..11e2b6876 100644 --- a/go.mod +++ b/go.mod @@ -92,6 +92,7 @@ require ( github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chigopher/pathlib v0.19.1 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect @@ -171,11 +172,13 @@ require ( github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huandu/skiplist v1.2.1 // indirect + github.com/huandu/xstrings v1.4.0 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/orderedmap v0.3.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect + github.com/jinzhu/copier v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect @@ -234,6 +237,7 @@ require ( github.com/tklauser/numcpus v0.6.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ulikunitz/xz v0.5.11 // indirect + github.com/vektra/mockery/v2 v2.53.4 // indirect github.com/zeebo/errs v1.4.0 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.4.0-alpha.1 // indirect @@ -252,6 +256,7 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/arch v0.17.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/mod v0.26.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/term v0.34.0 // indirect diff --git a/go.sum b/go.sum index 6321d356d..3813b5ae8 100644 --- a/go.sum +++ b/go.sum @@ -783,6 +783,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chigopher/pathlib v0.19.1 h1:RoLlUJc0CqBGwq239cilyhxPNLXTK+HXoASGyGznx5A= +github.com/chigopher/pathlib v0.19.1/go.mod h1:tzC1dZLW8o33UQpWkNkhvPwL5n4yyFRFm/jL1YGWFvY= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= @@ -1281,6 +1283,8 @@ github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3 github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= github.com/huandu/skiplist v1.2.1 h1:dTi93MgjwErA/8idWTzIw4Y1kZsMWx35fmI2c8Rij7w= github.com/huandu/skiplist v1.2.1/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= @@ -1309,6 +1313,8 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -1708,6 +1714,8 @@ github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/vektra/mockery/v2 v2.53.4 h1:abBWJLUQppM7T/VsLasBwgl7XXQRWH6lC3bnbJpOCLk= +github.com/vektra/mockery/v2 v2.53.4/go.mod h1:hIFFb3CvzPdDJJiU7J4zLRblUMv7OuezWsHPmswriwo= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= @@ -1879,6 +1887,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/local_node.sh b/local_node.sh index 6d3d0ba7c..6547fbf75 100755 --- a/local_node.sh +++ b/local_node.sh @@ -240,7 +240,7 @@ if [[ $overwrite == "y" || $overwrite == "Y" ]]; then jq '.app_state["bank"]["denom_metadata"]=[{"description":"The native staking token for evmd.","denom_units":[{"denom":"atest","exponent":0,"aliases":["attotest"]},{"denom":"test","exponent":18,"aliases":[]}],"base":"atest","display":"test","name":"Test Token","symbol":"TEST","uri":"","uri_hash":""}]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" - jq '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805", "0x0000000000000000000000000000000000000806", "0x0000000000000000000000000000000000000807"]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" + jq '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805", "0x0000000000000000000000000000000000000806", "0x0000000000000000000000000000000000000807", "0x0000000000000000000000000000000000000900"]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" jq '.app_state["evm"]["params"]["evm_denom"]="atest"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" diff --git a/precompiles/common/mocks/BankKeeper.go b/precompiles/common/mocks/BankKeeper.go index a3c72593c..2ad57e099 100644 --- a/precompiles/common/mocks/BankKeeper.go +++ b/precompiles/common/mocks/BankKeeper.go @@ -150,12 +150,49 @@ func (_m *BankKeeper) SpendableCoin(ctx context.Context, addr types.AccAddress, return r0 } +// MintCoins provides a mock function with given fields: ctx, moduleName, amt +func (_m *BankKeeper) MintCoins(ctx context.Context, moduleName string, amt types.Coins) error { + ret := _m.Called(ctx, moduleName, amt) + + if len(ret) == 0 { + panic("no return value specified for MintCoins") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, types.Coins) error); ok { + r0 = rf(ctx, moduleName, amt) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SendCoinsFromModuleToAccount provides a mock function with given fields: ctx, senderModule, recipientAddr, amt +func (_m *BankKeeper) SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr types.AccAddress, amt types.Coins) error { + ret := _m.Called(ctx, senderModule, recipientAddr, amt) + + if len(ret) == 0 { + panic("no return value specified for SendCoinsFromModuleToAccount") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, types.AccAddress, types.Coins) error); ok { + r0 = rf(ctx, senderModule, recipientAddr, amt) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // NewBankKeeper creates a new instance of BankKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewBankKeeper(t interface { mock.TestingT Cleanup(func()) -}) *BankKeeper { +}, +) *BankKeeper { mock := &BankKeeper{} mock.Mock.Test(t) diff --git a/precompiles/erc20factory/erc20factory.go b/precompiles/erc20factory/erc20factory.go index 0205e7a54..d7e38eff1 100644 --- a/precompiles/erc20factory/erc20factory.go +++ b/precompiles/erc20factory/erc20factory.go @@ -152,4 +152,4 @@ func (p *Precompile) HandleMethod( return nil, fmt.Errorf(cmn.ErrUnknownMethod, method.Name) } return bz, err -} \ No newline at end of file +} diff --git a/precompiles/erc20factory/events.go b/precompiles/erc20factory/events.go index 912189512..e9dd15d35 100644 --- a/precompiles/erc20factory/events.go +++ b/precompiles/erc20factory/events.go @@ -54,4 +54,4 @@ func (p Precompile) EmitCreateEvent(ctx sdk.Context, stateDB vm.StateDB, tokenAd }) return nil -} \ No newline at end of file +} diff --git a/precompiles/erc20factory/interfaces.go b/precompiles/erc20factory/interfaces.go index 920af5b41..abe08bced 100644 --- a/precompiles/erc20factory/interfaces.go +++ b/precompiles/erc20factory/interfaces.go @@ -22,4 +22,4 @@ type BankKeeper interface { SetDenomMetaData(ctx context.Context, denomMetaData banktypes.Metadata) MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error -} \ No newline at end of file +} diff --git a/precompiles/erc20factory/query.go b/precompiles/erc20factory/query.go index 15dbcbf82..5128ee205 100644 --- a/precompiles/erc20factory/query.go +++ b/precompiles/erc20factory/query.go @@ -26,4 +26,4 @@ func (p Precompile) CalculateAddress( address := crypto.CreateAddress2(caller, salt, calculateCodeHash(tokenType)) return method.Outputs.Pack(address) -} \ No newline at end of file +} diff --git a/precompiles/erc20factory/tx.go b/precompiles/erc20factory/tx.go index a2fc8a00b..2ee0a2315 100644 --- a/precompiles/erc20factory/tx.go +++ b/precompiles/erc20factory/tx.go @@ -57,7 +57,10 @@ func (p Precompile) Create( pair := erc20types.NewTokenPair(address, metadata.Name, erc20types.OWNER_EXTERNAL) - p.erc20Keeper.SetToken(ctx, pair) + err = p.erc20Keeper.SetToken(ctx, pair) + if err != nil { + return nil, err + } err = p.erc20Keeper.EnableDynamicPrecompile(ctx, pair.GetERC20Contract()) if err != nil { diff --git a/precompiles/erc20factory/types.go b/precompiles/erc20factory/types.go index fa186d342..9059a2989 100644 --- a/precompiles/erc20factory/types.go +++ b/precompiles/erc20factory/types.go @@ -98,4 +98,4 @@ func ParseCalculateAddressArgs(args []interface{}) (tokenType uint8, salt [32]ui } return tokenType, salt, nil -} \ No newline at end of file +} diff --git a/tests/integration/precompiles/erc20factory/test_erc20factory.go b/tests/integration/precompiles/erc20factory/test_erc20factory.go index 00c02a078..4f4afdd0a 100644 --- a/tests/integration/precompiles/erc20factory/test_erc20factory.go +++ b/tests/integration/precompiles/erc20factory/test_erc20factory.go @@ -7,6 +7,11 @@ import ( utiltx "github.com/cosmos/evm/testutil/tx" ) +const ( + tokenName = "Test" + tokenSymbol = "TEST" +) + func (s *PrecompileTestSuite) TestIsTransaction() { s.SetupTest() @@ -25,8 +30,8 @@ func (s *PrecompileTestSuite) TestRequiredGas() { mintAddr := utiltx.GenerateAddress() decimals := uint8(18) amount := big.NewInt(1000000) - name := "Test" - symbol := "TEST" + name := tokenName + symbol := tokenSymbol testcases := []struct { name string @@ -59,4 +64,4 @@ func (s *PrecompileTestSuite) TestRequiredGas() { s.Require().Equal(tc.expGas, gas) }) } -} \ No newline at end of file +} diff --git a/tests/integration/precompiles/erc20factory/test_events.go b/tests/integration/precompiles/erc20factory/test_events.go index 2ecddec34..0f012724e 100644 --- a/tests/integration/precompiles/erc20factory/test_events.go +++ b/tests/integration/precompiles/erc20factory/test_events.go @@ -67,4 +67,4 @@ func (s *PrecompileTestSuite) TestEmitCreateEvent() { s.Require().Equal(tc.premintedSupply, createEvent.PremintedSupply, "expected different preminted supply") }) } -} \ No newline at end of file +} diff --git a/tests/integration/precompiles/erc20factory/test_query.go b/tests/integration/precompiles/erc20factory/test_query.go index 0e376deeb..f4fcef4c5 100644 --- a/tests/integration/precompiles/erc20factory/test_query.go +++ b/tests/integration/precompiles/erc20factory/test_query.go @@ -1,8 +1,9 @@ package erc20factory import ( - "github.com/cosmos/evm/precompiles/erc20factory" "github.com/ethereum/go-ethereum/common" + + "github.com/cosmos/evm/precompiles/erc20factory" ) func (s *PrecompileTestSuite) TestCalculateAddress() { @@ -72,4 +73,4 @@ func (s *PrecompileTestSuite) TestCalculateAddress() { s.requireOut(bz, err, method, tc.expPass, tc.errContains, tc.expAddress) }) } -} \ No newline at end of file +} diff --git a/tests/integration/precompiles/erc20factory/test_setup.go b/tests/integration/precompiles/erc20factory/test_setup.go index b0d25ccb8..e8f07c504 100644 --- a/tests/integration/precompiles/erc20factory/test_setup.go +++ b/tests/integration/precompiles/erc20factory/test_setup.go @@ -1,12 +1,13 @@ package erc20factory import ( + "github.com/stretchr/testify/suite" + "github.com/cosmos/evm/precompiles/erc20factory" "github.com/cosmos/evm/testutil/integration/evm/factory" "github.com/cosmos/evm/testutil/integration/evm/grpc" "github.com/cosmos/evm/testutil/integration/evm/network" testkeyring "github.com/cosmos/evm/testutil/keyring" - "github.com/stretchr/testify/suite" ) // PrecompileTestSuite is the implementation of the TestSuite interface for ERC20 Factory precompile diff --git a/tests/integration/precompiles/erc20factory/test_tx.go b/tests/integration/precompiles/erc20factory/test_tx.go index 562d1f808..a74169470 100644 --- a/tests/integration/precompiles/erc20factory/test_tx.go +++ b/tests/integration/precompiles/erc20factory/test_tx.go @@ -3,11 +3,12 @@ package erc20factory import ( "math/big" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/cosmos/evm/precompiles/erc20factory" erc20types "github.com/cosmos/evm/x/erc20/types" + + sdk "github.com/cosmos/cosmos-sdk/types" ) func (s *PrecompileTestSuite) TestCreate() { @@ -45,7 +46,6 @@ func (s *PrecompileTestSuite) TestCreate() { s.Require().Equal(amount, balance.Amount.BigInt(), "expected balance to match preminted amount") s.Require().Equal(address.String(), expectedAddress, "expected address to match") - }, expAddress: expectedAddress, }, diff --git a/tests/integration/precompiles/erc20factory/test_types.go b/tests/integration/precompiles/erc20factory/test_types.go index 470ef0f61..1a3233e93 100644 --- a/tests/integration/precompiles/erc20factory/test_types.go +++ b/tests/integration/precompiles/erc20factory/test_types.go @@ -3,9 +3,10 @@ package erc20factory import ( "math/big" + "github.com/ethereum/go-ethereum/common" + "github.com/cosmos/evm/precompiles/erc20factory" utiltx "github.com/cosmos/evm/testutil/tx" - "github.com/ethereum/go-ethereum/common" ) func (s *PrecompileTestSuite) TestParseCalculateAddressArgs() { @@ -220,4 +221,4 @@ func (s *PrecompileTestSuite) TestParseCreateArgs() { } }) } -} \ No newline at end of file +} diff --git a/tests/integration/precompiles/erc20factory/test_utils.go b/tests/integration/precompiles/erc20factory/test_utils.go index fc1381a67..45b998553 100644 --- a/tests/integration/precompiles/erc20factory/test_utils.go +++ b/tests/integration/precompiles/erc20factory/test_utils.go @@ -3,8 +3,9 @@ package erc20factory import ( "math/big" - "github.com/cosmos/evm/precompiles/erc20factory" "github.com/ethereum/go-ethereum/accounts/abi" + + "github.com/cosmos/evm/precompiles/erc20factory" ) func (s *PrecompileTestSuite) setupERC20FactoryPrecompile() *erc20factory.Precompile { @@ -53,4 +54,4 @@ func (s *PrecompileTestSuite) requireOut( s.Require().Error(err, "expected error") s.Require().Contains(err.Error(), errContains, "expected different error") } -} \ No newline at end of file +} diff --git a/tests/jsonrpc/simulator/namespaces/eth.go b/tests/jsonrpc/simulator/namespaces/eth.go index f1922bc4c..d1e469b7c 100644 --- a/tests/jsonrpc/simulator/namespaces/eth.go +++ b/tests/jsonrpc/simulator/namespaces/eth.go @@ -1671,7 +1671,6 @@ func EthEstimateGas(rCtx *types.RPCContext) (*types.RpcResult, error) { func EthFeeHistory(rCtx *types.RPCContext) (*types.RpcResult, error) { var result interface{} err := rCtx.Evmd.RPCClient().Call(&result, string(MethodNameEthFeeHistory), "0x2", "latest", []float64{25.0, 50.0, 75.0}) - if err != nil { if err.Error() == "the method "+string(MethodNameEthFeeHistory)+" does not exist/is not available" || err.Error() == types.ErrorMethodNotFound { @@ -1926,7 +1925,6 @@ func EthGetHeaderByHash(rCtx *types.RPCContext) (*types.RpcResult, error) { var header any err = rCtx.Evmd.RPCClient().Call(&header, string(MethodNameEthGetHeaderByHash), receipt.BlockHash.Hex()) - if err != nil { if strings.Contains(err.Error(), "does not exist/is not available") || strings.Contains(err.Error(), "Method not found") { @@ -2004,7 +2002,6 @@ func EthGetHeaderByNumber(rCtx *types.RPCContext) (*types.RpcResult, error) { var header any err = rCtx.Evmd.RPCClient().Call(&header, string(MethodNameEthGetHeaderByNumber), blockNumberHex) - if err != nil { if strings.Contains(err.Error(), "does not exist/is not available") || strings.Contains(err.Error(), "Method not found") { @@ -2091,7 +2088,6 @@ func EthSimulateV1(rCtx *types.RPCContext) (*types.RpcResult, error) { var result any err := rCtx.Evmd.RPCClient().Call(&result, string(MethodNameEthSimulateV1), simulationReq) - if err != nil { if strings.Contains(err.Error(), "does not exist/is not available") || strings.Contains(err.Error(), "Method not found") || @@ -2158,7 +2154,6 @@ func EthPendingTransactions(rCtx *types.RPCContext) (*types.RpcResult, error) { var pendingTxs any err := rCtx.Evmd.RPCClient().Call(&pendingTxs, string(MethodNameEthPendingTransactions)) - if err != nil { if strings.Contains(err.Error(), "does not exist/is not available") || strings.Contains(err.Error(), "Method not found") || diff --git a/tests/jsonrpc/simulator/report/report.go b/tests/jsonrpc/simulator/report/report.go index d1f668bc7..20b664b2e 100644 --- a/tests/jsonrpc/simulator/report/report.go +++ b/tests/jsonrpc/simulator/report/report.go @@ -359,7 +359,6 @@ func PrintCategoryMatrix(summary *types.TestSummary) { catSummary.Total) } } - } func PrintSummary(summary *types.TestSummary) { diff --git a/tests/jsonrpc/simulator/types/context.go b/tests/jsonrpc/simulator/types/context.go index 79d0c2203..3a65245a2 100644 --- a/tests/jsonrpc/simulator/types/context.go +++ b/tests/jsonrpc/simulator/types/context.go @@ -77,7 +77,6 @@ type RPCContext struct { // Dual API testing fields EnableComparison bool // Enable dual API comparison ComparisonResults []*ComparisonResult // Store comparison results - } func NewRPCContext(conf *config.Config) (*RPCContext, error) { @@ -134,7 +133,6 @@ func (rCtx *RPCContext) AlreadyTested(rpc RpcName) *RpcResult { } } return nil - } // CompareRPCCall performs a dual API call and compares response structures diff --git a/tests/jsonrpc/simulator/utils/test_helpers.go b/tests/jsonrpc/simulator/utils/test_helpers.go index d584b116d..ea0bda5de 100644 --- a/tests/jsonrpc/simulator/utils/test_helpers.go +++ b/tests/jsonrpc/simulator/utils/test_helpers.go @@ -390,7 +390,6 @@ func Legacy(rCtx *types.RPCContext, methodName types.RpcName, category string, r // First test if the API is actually implemented var result interface{} err := rCtx.Evmd.RPCClient().Call(&result, string(methodName)) - if err != nil { // Check if it's a "method not found" error (API not implemented) if err.Error() == "the method "+string(methodName)+" does not exist/is not available" || diff --git a/tests/solidity/suites/precompiles/test/erc20factory.js b/tests/solidity/suites/precompiles/test/erc20factory.js index 079c18fb5..616f7cd9d 100644 --- a/tests/solidity/suites/precompiles/test/erc20factory.js +++ b/tests/solidity/suites/precompiles/test/erc20factory.js @@ -13,7 +13,9 @@ describe('ERC20Factory', function () { const salt = '0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234' const tokenPairType = 0 const erc20Factory = await hre.ethers.getContractAt('IERC20Factory', '0x0000000000000000000000000000000000000900') + console.log("erc20Factory contract loaded") const expectedAddress = await erc20Factory.calculateAddress(tokenPairType, salt) + console.log("erc20factory calculateAddress") expect(expectedAddress).to.equal('0x6a040655fE545126cD341506fCD4571dB3A444F9') }) @@ -24,7 +26,7 @@ describe('ERC20Factory', function () { const decimals = 18 const tokenPairType = 0 const premintedSupply = hre.ethers.parseEther("1000000") // 1M tokens - + 0x0000000000000000000000000000000000000900 const [signer] = await hre.ethers.getSigners() const minter = signer.address From 417987bfeccb1322821b3ab8287bdb0a45dde8d6 Mon Sep 17 00:00:00 2001 From: GuillemGarciaDev Date: Tue, 19 Aug 2025 11:44:45 +0200 Subject: [PATCH 5/8] fix(precompiles/erc20factory): add BalanceHandler to erc20factory precompile --- precompiles/erc20factory/IERC20Factory.sol | 8 +- precompiles/erc20factory/abi.json | 78 +++++++++---------- precompiles/erc20factory/erc20factory.go | 8 +- .../suites/precompiles/test/erc20factory.js | 1 - 4 files changed, 52 insertions(+), 43 deletions(-) diff --git a/precompiles/erc20factory/IERC20Factory.sol b/precompiles/erc20factory/IERC20Factory.sol index 1a2be905a..20af2bc5d 100644 --- a/precompiles/erc20factory/IERC20Factory.sol +++ b/precompiles/erc20factory/IERC20Factory.sol @@ -27,7 +27,9 @@ interface IERC20Factory { bytes32 salt, string name, string symbol, - uint8 decimals + uint8 decimals, + address minter, + uint256 premintedSupply ); /** @@ -44,7 +46,9 @@ interface IERC20Factory { bytes32 salt, string memory name, string memory symbol, - uint8 decimals + uint8 decimals, + address minter, + uint256 premintedSupply ) external returns (address tokenAddress); /** diff --git a/precompiles/erc20factory/abi.json b/precompiles/erc20factory/abi.json index d3db7dc53..998c8881a 100644 --- a/precompiles/erc20factory/abi.json +++ b/precompiles/erc20factory/abi.json @@ -4,68 +4,74 @@ "sourceName": "solidity/precompiles/erc20factory/IERC20Factory.sol", "abi": [ { + "anonymous": false, "inputs": [ { - "internalType": "uint8", - "name": "tokenPairType", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "salt", - "type": "bytes32" - } - ], - "name": "calculateAddress", - "outputs": [ - { + "indexed": true, "internalType": "address", "name": "tokenAddress", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + }, { + "indexed": false, "internalType": "uint8", "name": "tokenPairType", "type": "uint8" }, { + "indexed": false, "internalType": "bytes32", "name": "salt", "type": "bytes32" }, { + "indexed": false, "internalType": "string", "name": "name", "type": "string" }, { + "indexed": false, "internalType": "string", "name": "symbol", "type": "string" }, { + "indexed": false, "internalType": "uint8", "name": "decimals", "type": "uint8" }, { + "indexed": false, "internalType": "address", "name": "minter", "type": "address" }, { + "indexed": false, "internalType": "uint256", "name": "premintedSupply", "type": "uint256" } ], - "name": "create", + "name": "Create", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "tokenPairType", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "calculateAddress", "outputs": [ { "internalType": "address", @@ -73,67 +79,61 @@ "type": "address" } ], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { - "anonymous": false, "inputs": [ { - "indexed": true, - "internalType": "address", - "name": "tokenAddress", - "type": "address" - }, - { - "indexed": false, "internalType": "uint8", "name": "tokenPairType", "type": "uint8" }, { - "indexed": false, "internalType": "bytes32", "name": "salt", "type": "bytes32" }, { - "indexed": false, "internalType": "string", "name": "name", "type": "string" }, { - "indexed": false, "internalType": "string", "name": "symbol", "type": "string" }, { - "indexed": false, "internalType": "uint8", "name": "decimals", "type": "uint8" }, { - "indexed": false, "internalType": "address", "name": "minter", "type": "address" }, { - "indexed": false, "internalType": "uint256", "name": "premintedSupply", "type": "uint256" } ], - "name": "Create", - "type": "event" + "name": "create", + "outputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" } ], "bytecode": "0x", "deployedBytecode": "0x", "linkReferences": {}, "deployedLinkReferences": {} -} \ No newline at end of file +} diff --git a/precompiles/erc20factory/erc20factory.go b/precompiles/erc20factory/erc20factory.go index d7e38eff1..4225adeb4 100644 --- a/precompiles/erc20factory/erc20factory.go +++ b/precompiles/erc20factory/erc20factory.go @@ -40,7 +40,7 @@ type Precompile struct { // NewPrecompile creates a new bech32 Precompile instance as a // PrecompiledContract interface. -func NewPrecompile(erc20Keeper ERC20Keeper, bankKeeper BankKeeper) (*Precompile, error) { +func NewPrecompile(erc20Keeper ERC20Keeper, bankKeeper cmn.BankKeeper) (*Precompile, error) { newABI, err := cmn.LoadABI(f, "abi.json") if err != nil { return nil, err @@ -58,6 +58,8 @@ func NewPrecompile(erc20Keeper ERC20Keeper, bankKeeper BankKeeper) (*Precompile, // SetAddress defines the address of the distribution compile contract. p.SetAddress(common.HexToAddress(Erc20FactoryAddress)) + + p.SetBalanceHandler(bankKeeper) return p, nil } @@ -96,6 +98,10 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ if err != nil { return nil, err } + + // Start the balance change handler before executing the precompile + p.GetBalanceHandler().BeforeBalanceChange(ctx) + // This handles any out of gas errors that may occur during the execution of a precompile query. // It avoids panics and returns the out of gas error so the EVM can continue gracefully. defer cmn.HandleGasError(ctx, contract, initialGas, &err)() diff --git a/tests/solidity/suites/precompiles/test/erc20factory.js b/tests/solidity/suites/precompiles/test/erc20factory.js index 616f7cd9d..3ef085b16 100644 --- a/tests/solidity/suites/precompiles/test/erc20factory.js +++ b/tests/solidity/suites/precompiles/test/erc20factory.js @@ -26,7 +26,6 @@ describe('ERC20Factory', function () { const decimals = 18 const tokenPairType = 0 const premintedSupply = hre.ethers.parseEther("1000000") // 1M tokens - 0x0000000000000000000000000000000000000900 const [signer] = await hre.ethers.getSigners() const minter = signer.address From a5ba4a4922f921d380103003ed1584d353ddb073 Mon Sep 17 00:00:00 2001 From: GuillemGarciaDev Date: Fri, 5 Sep 2025 15:52:14 +0200 Subject: [PATCH 6/8] fix: remove `tokenType` parameter --- evmd/precompiles.go | 2 +- .../precompile_erc20_factory_test.go | 14 +++++ precompiles/erc20factory/IERC20Factory.sol | 7 +-- precompiles/erc20factory/abi.json | 16 ----- precompiles/erc20factory/erc20factory.go | 4 +- precompiles/erc20factory/events.go | 17 +++-- precompiles/erc20factory/interfaces.go | 4 ++ precompiles/erc20factory/query.go | 4 +- precompiles/erc20factory/tx.go | 23 +++---- precompiles/erc20factory/types.go | 62 ++++++++----------- .../erc20factory/test_erc20factory.go | 4 +- .../precompiles/erc20factory/test_events.go | 2 +- .../precompiles/erc20factory/test_query.go | 13 +--- .../precompiles/erc20factory/test_tx.go | 20 ++---- .../precompiles/erc20factory/test_types.go | 51 +++------------ .../precompiles/erc20factory/test_utils.go | 4 +- .../suites/precompiles/test/erc20factory.js | 15 ++--- x/erc20/types/errors.go | 1 + 18 files changed, 101 insertions(+), 162 deletions(-) create mode 100644 evmd/tests/integration/precompiles/erc20factory/precompile_erc20_factory_test.go diff --git a/evmd/precompiles.go b/evmd/precompiles.go index ea087072e..a5a40ca23 100644 --- a/evmd/precompiles.go +++ b/evmd/precompiles.go @@ -143,7 +143,7 @@ func NewAvailableStaticPrecompiles( panic(fmt.Errorf("failed to instantiate slashing precompile: %w", err)) } - erc20FactoryPrecompile, err := erc20factory.NewPrecompile(&erc20Keeper, bankKeeper) + erc20FactoryPrecompile, err := erc20factory.NewPrecompile(&erc20Keeper, bankKeeper, evmKeeper) if err != nil { panic(fmt.Errorf("failed to instantiate erc20 factory precompile: %w", err)) } diff --git a/evmd/tests/integration/precompiles/erc20factory/precompile_erc20_factory_test.go b/evmd/tests/integration/precompiles/erc20factory/precompile_erc20_factory_test.go new file mode 100644 index 000000000..26eb5d1c2 --- /dev/null +++ b/evmd/tests/integration/precompiles/erc20factory/precompile_erc20_factory_test.go @@ -0,0 +1,14 @@ +package erc20factory + +import ( + "testing" + + "github.com/cosmos/evm/evmd/tests/integration" + factory "github.com/cosmos/evm/tests/integration/precompiles/erc20factory" + "github.com/stretchr/testify/suite" +) + +func TestErc20FactoryPrecompileTestSuite(t *testing.T) { + s := factory.NewPrecompileTestSuite(integration.CreateEvmd) + suite.Run(t, s) +} diff --git a/precompiles/erc20factory/IERC20Factory.sol b/precompiles/erc20factory/IERC20Factory.sol index 20af2bc5d..55e840968 100644 --- a/precompiles/erc20factory/IERC20Factory.sol +++ b/precompiles/erc20factory/IERC20Factory.sol @@ -15,7 +15,6 @@ interface IERC20Factory { /** * @dev Emitted when a new ERC20 token is created. * @param tokenAddress The address of the ERC20 token. - * @param tokenPairType The type of token pair. * @param salt The salt used for deployment. * @param name The name of the token. * @param symbol The symbol of the token. @@ -23,7 +22,6 @@ interface IERC20Factory { */ event Create( address indexed tokenAddress, - uint8 tokenPairType, bytes32 salt, string name, string symbol, @@ -34,7 +32,6 @@ interface IERC20Factory { /** * @dev Defines a method for creating an ERC20 token. - * @param tokenPairType Token Pair type * @param salt Salt used for deployment * @param name The name of the token. * @param symbol The symbol of the token. @@ -42,7 +39,6 @@ interface IERC20Factory { * @return tokenAddress The ERC20 token address. */ function create( - uint8 tokenPairType, bytes32 salt, string memory name, string memory symbol, @@ -53,9 +49,8 @@ interface IERC20Factory { /** * @dev Calculates the deterministic address for a new token. - * @param tokenPairType Token Pair type * @param salt Salt used for deployment * @return tokenAddress The calculated ERC20 token address. */ - function calculateAddress(uint8 tokenPairType, bytes32 salt) external view returns (address tokenAddress); + function calculateAddress(bytes32 salt) external view returns (address tokenAddress); } \ No newline at end of file diff --git a/precompiles/erc20factory/abi.json b/precompiles/erc20factory/abi.json index 998c8881a..4ff56cdd6 100644 --- a/precompiles/erc20factory/abi.json +++ b/precompiles/erc20factory/abi.json @@ -12,12 +12,6 @@ "name": "tokenAddress", "type": "address" }, - { - "indexed": false, - "internalType": "uint8", - "name": "tokenPairType", - "type": "uint8" - }, { "indexed": false, "internalType": "bytes32", @@ -60,11 +54,6 @@ }, { "inputs": [ - { - "internalType": "uint8", - "name": "tokenPairType", - "type": "uint8" - }, { "internalType": "bytes32", "name": "salt", @@ -84,11 +73,6 @@ }, { "inputs": [ - { - "internalType": "uint8", - "name": "tokenPairType", - "type": "uint8" - }, { "internalType": "bytes32", "name": "salt", diff --git a/precompiles/erc20factory/erc20factory.go b/precompiles/erc20factory/erc20factory.go index 4225adeb4..fb93dd4d3 100644 --- a/precompiles/erc20factory/erc20factory.go +++ b/precompiles/erc20factory/erc20factory.go @@ -36,11 +36,12 @@ type Precompile struct { cmn.Precompile erc20Keeper ERC20Keeper bankKeeper BankKeeper + evmKeeper EvmKeeper } // NewPrecompile creates a new bech32 Precompile instance as a // PrecompiledContract interface. -func NewPrecompile(erc20Keeper ERC20Keeper, bankKeeper cmn.BankKeeper) (*Precompile, error) { +func NewPrecompile(erc20Keeper ERC20Keeper, bankKeeper cmn.BankKeeper, keeper EvmKeeper) (*Precompile, error) { newABI, err := cmn.LoadABI(f, "abi.json") if err != nil { return nil, err @@ -54,6 +55,7 @@ func NewPrecompile(erc20Keeper ERC20Keeper, bankKeeper cmn.BankKeeper) (*Precomp }, erc20Keeper: erc20Keeper, bankKeeper: bankKeeper, + evmKeeper: keeper, } // SetAddress defines the address of the distribution compile contract. diff --git a/precompiles/erc20factory/events.go b/precompiles/erc20factory/events.go index e9dd15d35..2f778b772 100644 --- a/precompiles/erc20factory/events.go +++ b/precompiles/erc20factory/events.go @@ -19,7 +19,7 @@ const ( ) // EmitCreateEvent emits the Create event. -func (p Precompile) EmitCreateEvent(ctx sdk.Context, stateDB vm.StateDB, tokenAddress common.Address, tokenType uint8, salt [32]uint8, name string, symbol string, decimals uint8, minter common.Address, premintedSupply *big.Int) error { +func (p Precompile) EmitCreateEvent(ctx sdk.Context, stateDB vm.StateDB, tokenAddress common.Address, salt [32]uint8, name string, symbol string, decimals uint8, minter common.Address, premintedSupply *big.Int) error { event := p.Events[EventTypeCreate] topics := make([]common.Hash, 2) // Only 2 topics: event ID + tokenAddress @@ -33,15 +33,14 @@ func (p Precompile) EmitCreateEvent(ctx sdk.Context, stateDB vm.StateDB, tokenAd // Pack the non-indexed event parameters into the data field arguments := abi.Arguments{ - event.Inputs[1], // tokenType - event.Inputs[2], // salt - event.Inputs[3], // name - event.Inputs[4], // symbol - event.Inputs[5], // decimals - event.Inputs[6], // minter - event.Inputs[7], // premintedSupply + event.Inputs[1], // salt + event.Inputs[2], // name + event.Inputs[3], // symbol + event.Inputs[4], // decimals + event.Inputs[5], // minter + event.Inputs[6], // premintedSupply } - packed, err := arguments.Pack(tokenType, salt, name, symbol, decimals, minter, premintedSupply) + packed, err := arguments.Pack(salt, name, symbol, decimals, minter, premintedSupply) if err != nil { return err } diff --git a/precompiles/erc20factory/interfaces.go b/precompiles/erc20factory/interfaces.go index abe08bced..0e80185f3 100644 --- a/precompiles/erc20factory/interfaces.go +++ b/precompiles/erc20factory/interfaces.go @@ -23,3 +23,7 @@ type BankKeeper interface { MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error } + +type EvmKeeper interface { + GetCodeHash(ctx sdk.Context, addr common.Address) common.Hash +} diff --git a/precompiles/erc20factory/query.go b/precompiles/erc20factory/query.go index 5128ee205..221841236 100644 --- a/precompiles/erc20factory/query.go +++ b/precompiles/erc20factory/query.go @@ -18,12 +18,12 @@ func (p Precompile) CalculateAddress( caller common.Address, args []interface{}, ) ([]byte, error) { - tokenType, salt, err := ParseCalculateAddressArgs(args) + salt, err := ParseCalculateAddressArgs(args) if err != nil { return nil, err } - address := crypto.CreateAddress2(caller, salt, calculateCodeHash(tokenType)) + address := crypto.CreateAddress2(caller, salt, []byte{}) return method.Outputs.Pack(address) } diff --git a/precompiles/erc20factory/tx.go b/precompiles/erc20factory/tx.go index 2ee0a2315..d42386362 100644 --- a/precompiles/erc20factory/tx.go +++ b/precompiles/erc20factory/tx.go @@ -4,14 +4,13 @@ package erc20factory import ( - "encoding/binary" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" erc20types "github.com/cosmos/evm/x/erc20/types" + evmtypes "github.com/cosmos/evm/x/vm/types" "cosmossdk.io/errors" "cosmossdk.io/math" @@ -33,12 +32,20 @@ func (p Precompile) Create( caller common.Address, args []interface{}, ) ([]byte, error) { - tokenType, salt, name, symbol, decimals, minter, premintedSupply, err := ParseCreateArgs(args) + salt, name, symbol, decimals, minter, premintedSupply, err := ParseCreateArgs(args) if err != nil { return nil, err } - address := crypto.CreateAddress2(caller, salt, calculateCodeHash(tokenType)) + address := crypto.CreateAddress2(caller, salt, []byte{}) + + hash := p.evmKeeper.GetCodeHash(ctx, address) + if hash.Cmp(common.BytesToHash(evmtypes.EmptyCodeHash)) != 0 { + return nil, errors.Wrapf( + erc20types.ErrContractAlreadyExists, + "contract already exists at address %s", address.String(), + ) + } metadata, err := p.createCoinMetadata(ctx, address, name, symbol, decimals) if err != nil { @@ -75,7 +82,7 @@ func (p Precompile) Create( return nil, err } - if err = p.EmitCreateEvent(ctx, stateDB, address, tokenType, salt, name, symbol, decimals, minter, premintedSupply); err != nil { + if err = p.EmitCreateEvent(ctx, stateDB, address, salt, name, symbol, decimals, minter, premintedSupply); err != nil { return nil, err } @@ -135,9 +142,3 @@ func (p Precompile) createCoinMetadata(ctx sdk.Context, address common.Address, return &metadata, nil } - -func calculateCodeHash(tokenType uint8) []byte { - tokenTypeBytes := make([]byte, 4) - binary.LittleEndian.PutUint32(tokenTypeBytes, uint32(tokenType)) - return tokenTypeBytes -} diff --git a/precompiles/erc20factory/types.go b/precompiles/erc20factory/types.go index 9059a2989..0feaba006 100644 --- a/precompiles/erc20factory/types.go +++ b/precompiles/erc20factory/types.go @@ -23,79 +23,69 @@ type EventCreate struct { // ParseCreateArgs parses the arguments from the create method and returns // the token type, salt, name, symbol, decimals, minter, and preminted supply. -func ParseCreateArgs(args []interface{}) (tokenType uint8, salt [32]uint8, name string, symbol string, decimals uint8, minter common.Address, premintedSupply *big.Int, err error) { - if len(args) != 7 { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 7, len(args)) +func ParseCreateArgs(args []interface{}) (salt [32]uint8, name string, symbol string, decimals uint8, minter common.Address, premintedSupply *big.Int, err error) { + if len(args) != 6 { + return [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 7, len(args)) } - tokenType, ok := args[0].(uint8) + salt, ok := args[0].([32]uint8) if !ok { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid tokenType") + return [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid salt") } - salt, ok = args[1].([32]uint8) - if !ok { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid salt") - } - - name, ok = args[2].(string) + name, ok = args[1].(string) if !ok || len(name) < 3 || len(name) > 128 { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid name") + return [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid name") } - symbol, ok = args[3].(string) - if !ok || len(symbol) < 3 || len(symbol) > 16 { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid symbol") + symbol, ok = args[2].(string) + if !ok || len(symbol) < 1 || len(symbol) > 16 { + return [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid symbol") } - decimals, ok = args[4].(uint8) + decimals, ok = args[3].(uint8) if !ok { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid decimals") + return [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid decimals") } - minter, ok = args[5].(common.Address) + minter, ok = args[4].(common.Address) if !ok { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid minter") + return [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid minter") } // Validate that minter is not the zero address if minter == (common.Address{}) { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid minter: cannot be zero address") + return [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid minter: cannot be zero address") } - premintedSupply, ok = args[6].(*big.Int) + premintedSupply, ok = args[5].(*big.Int) if !ok { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid premintedSupply: expected *big.Int") + return [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid premintedSupply: expected *big.Int") } if premintedSupply.Sign() < 0 { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid premintedSupply: cannot be negative") + return [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("invalid premintedSupply: cannot be negative") } maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) if premintedSupply.Cmp(maxUint256) > 0 { - return uint8(0), [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("premintedSupply exceeds uint256 maximum") + return [32]uint8{}, "", "", uint8(0), common.Address{}, nil, fmt.Errorf("premintedSupply exceeds uint256 maximum") } - return tokenType, salt, name, symbol, decimals, minter, premintedSupply, nil + return salt, name, symbol, decimals, minter, premintedSupply, nil } // ParseCalculateAddressArgs parses the arguments from the calculateAddress method and returns // the token type and salt. -func ParseCalculateAddressArgs(args []interface{}) (tokenType uint8, salt [32]uint8, err error) { - if len(args) != 2 { - return uint8(0), [32]uint8{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 2, len(args)) - } - - tokenType, ok := args[0].(uint8) - if !ok { - return uint8(0), [32]uint8{}, fmt.Errorf("invalid tokenType") +func ParseCalculateAddressArgs(args []interface{}) (salt [32]uint8, err error) { + if len(args) != 1 { + return [32]uint8{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 2, len(args)) } - salt, ok = args[1].([32]uint8) + salt, ok := args[0].([32]uint8) if !ok { - return uint8(0), [32]uint8{}, fmt.Errorf("invalid salt") + return [32]uint8{}, fmt.Errorf("invalid salt") } - return tokenType, salt, nil + return salt, nil } diff --git a/tests/integration/precompiles/erc20factory/test_erc20factory.go b/tests/integration/precompiles/erc20factory/test_erc20factory.go index 4f4afdd0a..41ca6ce21 100644 --- a/tests/integration/precompiles/erc20factory/test_erc20factory.go +++ b/tests/integration/precompiles/erc20factory/test_erc20factory.go @@ -41,7 +41,7 @@ func (s *PrecompileTestSuite) TestRequiredGas() { { name: erc20factory.CalculateAddressMethod, malleate: func() []byte { - bz, err := s.precompile.Pack(erc20factory.CalculateAddressMethod, uint8(0), [32]uint8{}) + bz, err := s.precompile.Pack(erc20factory.CalculateAddressMethod, [32]uint8{}) s.Require().NoError(err, "expected no error packing ABI") return bz }, @@ -50,7 +50,7 @@ func (s *PrecompileTestSuite) TestRequiredGas() { { name: erc20factory.CreateMethod, malleate: func() []byte { - bz, err := s.precompile.Pack(erc20factory.CreateMethod, uint8(0), [32]uint8{}, name, symbol, decimals, mintAddr, amount) + bz, err := s.precompile.Pack(erc20factory.CreateMethod, [32]uint8{}, name, symbol, decimals, mintAddr, amount) s.Require().NoError(err, "expected no error packing ABI") return bz }, diff --git a/tests/integration/precompiles/erc20factory/test_events.go b/tests/integration/precompiles/erc20factory/test_events.go index 0f012724e..1fd8cd857 100644 --- a/tests/integration/precompiles/erc20factory/test_events.go +++ b/tests/integration/precompiles/erc20factory/test_events.go @@ -41,7 +41,7 @@ func (s *PrecompileTestSuite) TestEmitCreateEvent() { s.SetupTest() stateDB := s.network.GetStateDB() - err := s.precompile.EmitCreateEvent(s.network.GetContext(), stateDB, tc.tokenAddress, tc.tokenType, tc.salt, tc.name, tc.symbol, tc.decimals, tc.minter, tc.premintedSupply) + err := s.precompile.EmitCreateEvent(s.network.GetContext(), stateDB, tc.tokenAddress, tc.salt, tc.name, tc.symbol, tc.decimals, tc.minter, tc.premintedSupply) s.Require().NoError(err, "expected create event to be emitted successfully") log := stateDB.Logs()[0] diff --git a/tests/integration/precompiles/erc20factory/test_query.go b/tests/integration/precompiles/erc20factory/test_query.go index f4fcef4c5..fd17eb274 100644 --- a/tests/integration/precompiles/erc20factory/test_query.go +++ b/tests/integration/precompiles/erc20factory/test_query.go @@ -21,26 +21,15 @@ func (s *PrecompileTestSuite) TestCalculateAddress() { name: "pass - correct arguments", caller: defaultCaller, args: []interface{}{ - uint8(0), [32]uint8(common.HexToHash("0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234").Bytes()), }, expPass: true, - expAddress: common.HexToAddress("0x188a919f3583f8e02183332E6c73E944E002C553"), - }, - { - name: "fail - invalid tokenType", - caller: defaultCaller, - args: []interface{}{ - "invalid tokenType", - "invalid salt", - }, - errContains: "invalid tokenType", + expAddress: common.HexToAddress("0xc047E2F9302F4dE42115E40CEdb3FA0F1CfbD6b7"), }, { name: "fail - invalid salt", caller: defaultCaller, args: []interface{}{ - uint8(0), "invalid salt", }, errContains: "invalid salt", diff --git a/tests/integration/precompiles/erc20factory/test_tx.go b/tests/integration/precompiles/erc20factory/test_tx.go index a74169470..e83b83982 100644 --- a/tests/integration/precompiles/erc20factory/test_tx.go +++ b/tests/integration/precompiles/erc20factory/test_tx.go @@ -14,7 +14,7 @@ import ( func (s *PrecompileTestSuite) TestCreate() { caller := common.HexToAddress("0x2c7882f69Cd115F470aAEde121f57F932936a56f") mintAddr := common.HexToAddress("0x73657398D483143AF7db7899757e5E7037fB713d") - expectedAddress := common.HexToAddress("0x30E56567F73403eD713dA0b0419e4A5330A16896") + expectedAddress := common.HexToAddress("0xc5ecc46b3cf020351c2186afCD5C734EE15E4da2") amount := big.NewInt(1000000) decimals := uint8(18) name := "Test" @@ -32,7 +32,7 @@ func (s *PrecompileTestSuite) TestCreate() { }{ { name: "pass - correct arguments", - args: []interface{}{uint8(0), [32]uint8(common.HexToHash("0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234").Bytes()), name, symbol, decimals, mintAddr, amount}, + args: []interface{}{[32]uint8(common.HexToHash("0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234").Bytes()), name, symbol, decimals, mintAddr, amount}, expPass: true, postExpPass: func(output []byte) { res, err := method.Outputs.Unpack(output) @@ -50,22 +50,19 @@ func (s *PrecompileTestSuite) TestCreate() { expAddress: expectedAddress, }, { - name: "fail - invalid tokenType", + name: "fail - blocked addresses cannot receive tokens", args: []interface{}{ - "invalid tokenType", - [32]uint8{}, + [32]uint8(common.HexToHash("0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234").Bytes()), name, symbol, decimals, - mintAddr, + erc20types.ModuleAddress, amount, }, - errContains: "invalid tokenType", }, { name: "fail - invalid salt", args: []interface{}{ - uint8(0), "invalid salt", name, symbol, @@ -78,7 +75,6 @@ func (s *PrecompileTestSuite) TestCreate() { { name: "fail - invalid name", args: []interface{}{ - uint8(0), [32]uint8{}, "", symbol, @@ -91,10 +87,9 @@ func (s *PrecompileTestSuite) TestCreate() { { name: "fail - invalid symbol", args: []interface{}{ - uint8(0), [32]uint8{}, name, - "is", + "", decimals, mintAddr, amount, @@ -104,7 +99,6 @@ func (s *PrecompileTestSuite) TestCreate() { { name: "fail - invalid decimals", args: []interface{}{ - uint8(0), [32]uint8{}, name, symbol, @@ -117,7 +111,6 @@ func (s *PrecompileTestSuite) TestCreate() { { name: "fail - invalid minter", args: []interface{}{ - uint8(0), [32]uint8{}, name, symbol, @@ -130,7 +123,6 @@ func (s *PrecompileTestSuite) TestCreate() { { name: "fail - invalid preminted supply", args: []interface{}{ - uint8(0), [32]uint8{}, name, symbol, diff --git a/tests/integration/precompiles/erc20factory/test_types.go b/tests/integration/precompiles/erc20factory/test_types.go index 1a3233e93..bc665c95f 100644 --- a/tests/integration/precompiles/erc20factory/test_types.go +++ b/tests/integration/precompiles/erc20factory/test_types.go @@ -21,19 +21,10 @@ func (s *PrecompileTestSuite) TestParseCalculateAddressArgs() { { name: "pass - correct arguments", args: []interface{}{ - uint8(0), [32]uint8{}, }, expPass: true, }, - { - name: "fail - invalid tokenType", - args: []interface{}{ - "invalid tokenType", - [32]uint8{}, - }, - errContains: "invalid tokenType", - }, { name: "fail - invalid salt", args: []interface{}{ @@ -52,11 +43,10 @@ func (s *PrecompileTestSuite) TestParseCalculateAddressArgs() { for _, tc := range testcases { s.Run(tc.name, func() { - tokenType, salt, err := erc20factory.ParseCalculateAddressArgs(tc.args) + salt, err := erc20factory.ParseCalculateAddressArgs(tc.args) if tc.expPass { s.Require().NoError(err, "unexpected error parsing the calculate address arguments") - s.Require().Equal(tokenType, tc.args[0], "expected different token type") - s.Require().Equal(salt, tc.args[1], "expected different salt") + s.Require().Equal(salt, tc.args[0], "expected different salt") } else { s.Require().Error(err, "expected an error parsing the calculate address arguments") s.Require().ErrorContains(err, tc.errContains, "expected different error message") @@ -83,7 +73,6 @@ func (s *PrecompileTestSuite) TestParseCreateArgs() { { name: "pass - correct arguments", args: []interface{}{ - uint8(0), [32]uint8{}, name, symbol, @@ -93,22 +82,9 @@ func (s *PrecompileTestSuite) TestParseCreateArgs() { }, expPass: true, }, - { - name: "fail - invalid tokenType", - args: []interface{}{ - "invalid tokenType", - [32]uint8{}, - name, - symbol, - decimals, - addr, - big.NewInt(1000000), - }, - }, { name: "fail - invalid salt", args: []interface{}{ - uint8(0), "invalid salt", name, symbol, @@ -120,7 +96,6 @@ func (s *PrecompileTestSuite) TestParseCreateArgs() { { name: "fail - invalid name", args: []interface{}{ - uint8(0), [32]uint8{}, uint8(0), symbol, @@ -133,10 +108,9 @@ func (s *PrecompileTestSuite) TestParseCreateArgs() { { name: "fail - invalid symbol", args: []interface{}{ - uint8(0), [32]uint8{}, name, - "is", + "", decimals, addr, big.NewInt(1000000), @@ -146,7 +120,6 @@ func (s *PrecompileTestSuite) TestParseCreateArgs() { { name: "fail - invalid decimals", args: []interface{}{ - uint8(0), [32]uint8{}, name, symbol, @@ -159,7 +132,6 @@ func (s *PrecompileTestSuite) TestParseCreateArgs() { { name: "fail - invalid minter", args: []interface{}{ - uint8(0), [32]uint8{}, name, symbol, @@ -172,7 +144,6 @@ func (s *PrecompileTestSuite) TestParseCreateArgs() { { name: "fail - zero address minter", args: []interface{}{ - uint8(0), [32]uint8{}, name, symbol, @@ -185,7 +156,6 @@ func (s *PrecompileTestSuite) TestParseCreateArgs() { { name: "fail - invalid preminted supply", args: []interface{}{ - uint8(0), [32]uint8{}, name, symbol, @@ -205,16 +175,15 @@ func (s *PrecompileTestSuite) TestParseCreateArgs() { for _, tc := range testcases { s.Run(tc.name, func() { - tokenType, salt, name, symbol, decimals, minter, premintedSupply, err := erc20factory.ParseCreateArgs(tc.args) + salt, name, symbol, decimals, minter, premintedSupply, err := erc20factory.ParseCreateArgs(tc.args) if tc.expPass { s.Require().NoError(err, "unexpected error parsing the create arguments") - s.Require().Equal(tokenType, tc.args[0], "expected different token type") - s.Require().Equal(salt, tc.args[1], "expected different salt") - s.Require().Equal(name, tc.args[2], "expected different name") - s.Require().Equal(symbol, tc.args[3], "expected different symbol") - s.Require().Equal(decimals, tc.args[4], "expected different decimals") - s.Require().Equal(minter, tc.args[5], "expected different minter") - s.Require().Equal(premintedSupply, tc.args[6], "expected different preminted supply") + s.Require().Equal(salt, tc.args[0], "expected different salt") + s.Require().Equal(name, tc.args[1], "expected different name") + s.Require().Equal(symbol, tc.args[2], "expected different symbol") + s.Require().Equal(decimals, tc.args[3], "expected different decimals") + s.Require().Equal(minter, tc.args[4], "expected different minter") + s.Require().Equal(premintedSupply, tc.args[5], "expected different preminted supply") } else { s.Require().Error(err, "expected an error parsing the create arguments") s.Require().ErrorContains(err, tc.errContains, "expected different error message") diff --git a/tests/integration/precompiles/erc20factory/test_utils.go b/tests/integration/precompiles/erc20factory/test_utils.go index 45b998553..c85d95aa1 100644 --- a/tests/integration/precompiles/erc20factory/test_utils.go +++ b/tests/integration/precompiles/erc20factory/test_utils.go @@ -11,7 +11,9 @@ import ( func (s *PrecompileTestSuite) setupERC20FactoryPrecompile() *erc20factory.Precompile { precompile, err := erc20factory.NewPrecompile( s.network.App.GetErc20Keeper(), - s.network.App.GetBankKeeper()) + s.network.App.GetBankKeeper(), + s.network.App.GetEVMKeeper(), + ) s.Require().NoError(err, "failed to create erc20factory precompile") return precompile diff --git a/tests/solidity/suites/precompiles/test/erc20factory.js b/tests/solidity/suites/precompiles/test/erc20factory.js index 3ef085b16..9a5402dc9 100644 --- a/tests/solidity/suites/precompiles/test/erc20factory.js +++ b/tests/solidity/suites/precompiles/test/erc20factory.js @@ -2,19 +2,18 @@ const { expect } = require('chai') const hre = require('hardhat') const abi = [ - "function create(uint8 tokenPairType, bytes32 salt, string memory name, string memory symbol, uint8 decimals, address minter, uint256 premintedSupply) external returns (address)", - "function calculateAddress(uint8 tokenPairType, bytes32 salt) external view returns (address)", - "event Create(address indexed tokenAddress, uint8 tokenPairType, bytes32 salt, string name, string symbol, uint8 decimals, address minter, uint256 premintedSupply)" + "function create(bytes32 salt, string memory name, string memory symbol, uint8 decimals, address minter, uint256 premintedSupply) external returns (address)", + "function calculateAddress(bytes32 salt) external view returns (address)", + "event Create(address indexed tokenAddress, bytes32 salt, string name, string symbol, uint8 decimals, address minter, uint256 premintedSupply)" ] describe('ERC20Factory', function () { it('should calculate the correct address', async function () { const salt = '0x4f5b6f778b28c4d67a9c12345678901234567890123456789012345678901234' - const tokenPairType = 0 const erc20Factory = await hre.ethers.getContractAt('IERC20Factory', '0x0000000000000000000000000000000000000900') console.log("erc20Factory contract loaded") - const expectedAddress = await erc20Factory.calculateAddress(tokenPairType, salt) + const expectedAddress = await erc20Factory.calculateAddress(salt) console.log("erc20factory calculateAddress") expect(expectedAddress).to.equal('0x6a040655fE545126cD341506fCD4571dB3A444F9') }) @@ -24,7 +23,6 @@ describe('ERC20Factory', function () { const name = 'Test' const symbol = 'TEST' const decimals = 18 - const tokenPairType = 0 const premintedSupply = hre.ethers.parseEther("1000000") // 1M tokens const [signer] = await hre.ethers.getSigners() const minter = signer.address @@ -32,8 +30,8 @@ describe('ERC20Factory', function () { // Calculate the expected token address before deployment const erc20Factory = await hre.ethers.getContractAt('IERC20Factory', '0x0000000000000000000000000000000000000900') - const tokenAddress = await erc20Factory.calculateAddress(tokenPairType, salt) - const tx = await erc20Factory.connect(signer).create(tokenPairType, salt, name, symbol, decimals, minter, premintedSupply) + const tokenAddress = await erc20Factory.calculateAddress(salt) + const tx = await erc20Factory.connect(signer).create(salt, name, symbol, decimals, minter, premintedSupply) // Get the token address from the transaction receipt const receipt = await tx.wait() @@ -46,7 +44,6 @@ describe('ERC20Factory', function () { const createEvents = await erc20FactoryWithEvents.queryFilter(erc20FactoryWithEvents.filters.Create(), receipt.blockNumber, receipt.blockNumber) expect(createEvents.length).to.equal(1) expect(createEvents[0].args.tokenAddress).to.equal(tokenAddress) - expect(createEvents[0].args.tokenPairType).to.equal(tokenPairType) expect(createEvents[0].args.salt).to.equal(salt) expect(createEvents[0].args.name).to.equal(name) expect(createEvents[0].args.symbol).to.equal(symbol) diff --git a/x/erc20/types/errors.go b/x/erc20/types/errors.go index 99ad293f2..7f50187f9 100644 --- a/x/erc20/types/errors.go +++ b/x/erc20/types/errors.go @@ -25,4 +25,5 @@ var ( ErrInvalidAllowance = errorsmod.Register(ModuleName, 18, "invalid allowance") ErrNegativeToken = errorsmod.Register(ModuleName, 19, "token amount is negative") ErrExpectedEvent = errorsmod.Register(ModuleName, 20, "expected event") + ErrContractAlreadyExists = errorsmod.Register(ModuleName, 21, "contract already exists") ) From baed74a85d3ffa0294aeca1c952899888567e25b Mon Sep 17 00:00:00 2001 From: GuillemGarciaDev Date: Tue, 9 Sep 2025 10:16:57 +0200 Subject: [PATCH 7/8] fix(tests/solidity): fix erc20factory calculate address test --- tests/solidity/suites/precompiles/test/erc20factory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/solidity/suites/precompiles/test/erc20factory.js b/tests/solidity/suites/precompiles/test/erc20factory.js index 9a5402dc9..eac7d3bb1 100644 --- a/tests/solidity/suites/precompiles/test/erc20factory.js +++ b/tests/solidity/suites/precompiles/test/erc20factory.js @@ -15,7 +15,7 @@ describe('ERC20Factory', function () { console.log("erc20Factory contract loaded") const expectedAddress = await erc20Factory.calculateAddress(salt) console.log("erc20factory calculateAddress") - expect(expectedAddress).to.equal('0x6a040655fE545126cD341506fCD4571dB3A444F9') + expect(expectedAddress).to.equal('0x8C9521848ee0d03BF47390F98c6dc968DA0b2915') }) it('should create a new ERC20 token', async function () { From 1b604250a60219fc83f6f14ebfcfa7dc7a96a6ff Mon Sep 17 00:00:00 2001 From: GuillemGarciaDev Date: Tue, 16 Sep 2025 09:29:01 +0200 Subject: [PATCH 8/8] chore: add erc20 factory precompile feature to CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 315d4f9ed..cbaa131f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - [\#346](https://github.com/cosmos/evm/pull/346) Add eth_createAccessList method and implementation - [\#502](https://github.com/cosmos/evm/pull/502) Add block time in derived logs. +- [\#405](https://github.com/cosmos/evm/pull/405) Add erc20 factory precompile. ### STATE BREAKING