diff --git a/CHANGELOG.md b/CHANGELOG.md index aa67b50b0..cf3961a3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1960](https://github.com/NibiruChain/nibiru/pull/1960) - test(network): graceful cleanup for more consistent CI runs - [#1961](https://github.com/NibiruChain/nibiru/pull/1961) - chore(test): reverted funtoken precompile test back to the isolated state - [#1962](https://github.com/NibiruChain/nibiru/pull/1962) - chore(evm): code cleanup, unused code, typos, styles, warnings +- [#1963](https://github.com/NibiruChain/nibiru/pull/1963) - feat(evm): Deduct a fee during the creation of a FunToken mapping. Implemented by `deductCreateFunTokenFee` inside of the `eth.evm.v1.MsgCreateFunToken` transaction. #### Dapp modules: perp, spot, oracle, etc diff --git a/proto/eth/evm/v1/evm.proto b/proto/eth/evm/v1/evm.proto index 4ce9aeeda..01dfd6708 100644 --- a/proto/eth/evm/v1/evm.proto +++ b/proto/eth/evm/v1/evm.proto @@ -32,9 +32,11 @@ message Params { // evm_denom represents the token denomination used to run the EVM state // transitions. string evm_denom = 1 [(gogoproto.moretags) = "yaml:\"evm_denom\""]; - // enable_create toggles state transitions that use the vm.Create function + // enable_create: Enables contract creation by allowing for state transitions + // that use the "vm.Create" function bool enable_create = 2 [(gogoproto.moretags) = "yaml:\"enable_create\""]; - // enable_call toggles state transitions that use the vm.Call function + // enable_call: Enables contract calls by allowing for state transitions that + // use the "vm.Call" function bool enable_call = 3 [(gogoproto.moretags) = "yaml:\"enable_call\""]; // extra_eips defines the additional EIPs for the vm.Config repeated int64 extra_eips = 4 [(gogoproto.customname) = "ExtraEIPs", (gogoproto.moretags) = "yaml:\"extra_eips\""]; @@ -48,6 +50,14 @@ message Params { repeated string active_precompiles = 7; // evm_channels is the list of channel identifiers from EVM compatible chains repeated string evm_channels = 8 [(gogoproto.customname) = "EVMChannels"]; + + // Fee deducted and burned when calling "CreateFunToken" in units of + // "evm_denom". + string create_funtoken_fee = 9 [ + (gogoproto.customtype) = "cosmossdk.io/math.Int", + (gogoproto.nullable) = false + ]; + } // State represents a single Storage key value pair item. diff --git a/proto/eth/evm/v1/tx.proto b/proto/eth/evm/v1/tx.proto index d3b3160d7..1ef1d7f29 100644 --- a/proto/eth/evm/v1/tx.proto +++ b/proto/eth/evm/v1/tx.proto @@ -181,7 +181,7 @@ message MsgUpdateParams { // params defines the x/evm parameters to update. // NOTE: All parameters must be supplied. - Params params = 2 [(gogoproto.nullable) = false]; + eth.evm.v1.Params params = 2 [(gogoproto.nullable) = false]; } // MsgUpdateParamsResponse defines the response structure for executing a diff --git a/x/common/testutil/testnetwork/network.go b/x/common/testutil/testnetwork/network.go index 8ecfec073..44c1f6ac2 100644 --- a/x/common/testutil/testnetwork/network.go +++ b/x/common/testutil/testnetwork/network.go @@ -593,14 +593,15 @@ func (n *Network) Cleanup() { waitGroup.Wait() - // TODO: Is there a cleaner way to do this with a synchronous check? + // TODO: Is there a cleaner way to do this with a guarnteed synchronous on + // each "Validator.tmNode"? // https://github.com/NibiruChain/nibiru/issues/1955 // Give a brief pause for things to finish closing in other processes. // Hopefully this helps with the address-in-use errors. // Timeout of 100ms chosen randomly. // Timeout of 250ms chosen because 100ms was not enough. | 2024-07-02 - maxRetries := 5 + maxRetries := 20 stopped := false for i := 0; i < maxRetries; i++ { if ValidatorsStopped(n.Validators) { diff --git a/x/common/testutil/testnetwork/validator_node.go b/x/common/testutil/testnetwork/validator_node.go index 932201429..9062cd43d 100644 --- a/x/common/testutil/testnetwork/validator_node.go +++ b/x/common/testutil/testnetwork/validator_node.go @@ -128,8 +128,12 @@ func stopValidatorNode(v *Validator) { // accepting new connections and RPCs and blocks until all the pending RPCs are // finished. v.grpc.GracefulStop() - if v.grpcWeb != nil { - _ = v.grpcWeb.Close() + } + + if v.grpcWeb != nil { + err := v.grpcWeb.Close() + if err != nil { + v.Logger.Logf("❌ Error closing the gRPC web server: %w", err) } } diff --git a/x/evm/embeds/ERC20Minter.sol b/x/evm/embeds/ERC20Minter.sol index 90aed1e04..e2ed31034 100644 --- a/x/evm/embeds/ERC20Minter.sol +++ b/x/evm/embeds/ERC20Minter.sol @@ -9,22 +9,20 @@ import "@openzeppelin/contracts/access/Ownable.sol"; /** * @dev {ERC20} token, including: * + * - an "owner" that can mint tokens * - ability for holders to burn (destroy) their tokens - * - a minter role that allows for token minting (creation) * * The contract owner is set automatically in the constructor as the * deployer due to "Ownable". * * The Context contract is inherited indirectly through "ERC20" and "Ownable". - * - * The account that deploys the contract will be able to mint and burn tokens. */ contract ERC20Minter is ERC20, ERC20Burnable, Ownable { uint8 private _decimals; /** - * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` to the - * account that deploys the contract and customizes tokens decimals + * @dev Grants "owner" status to the account that deploys the contract and + * customizes tokens decimals. * * See {ERC20-constructor}. */ @@ -52,24 +50,21 @@ contract ERC20Minter is ERC20, ERC20Burnable, Ownable { * * See {ERC20-_mint}. * - * Requirements: - * - * - the caller must have the `MINTER_ROLE`. */ function mint(address to, uint256 amount) public virtual onlyOwner { _mint(to, amount); } /** - * @dev Destroys `amount` new tokens for `to`. + * @dev Destroys `amount` new tokens for `to`. Suitable when the contract owner + * should have authority to burn tokens from an account directly, such as in + * the case of regulatory compliance, or actions selected via + * decentralized governance. * * See {ERC20-_burn}. * - * Requirements: - * - * - the caller must have the `MINTER_ROLE`. */ - function burnCoins(address from, uint256 amount) public virtual onlyOwner { + function burnFromAuthority(address from, uint256 amount) public virtual onlyOwner { _burn(from, amount); } diff --git a/x/evm/embeds/ERC20MinterCompiled.json b/x/evm/embeds/ERC20MinterCompiled.json index 2dff40e72..37333cc03 100644 --- a/x/evm/embeds/ERC20MinterCompiled.json +++ b/x/evm/embeds/ERC20MinterCompiled.json @@ -186,7 +186,7 @@ "type": "uint256" } ], - "name": "burnCoins", + "name": "burnFromAuthority", "outputs": [], "stateMutability": "nonpayable", "type": "function" diff --git a/x/evm/errors.go b/x/evm/errors.go index 9e47690e8..dc07167c6 100644 --- a/x/evm/errors.go +++ b/x/evm/errors.go @@ -12,6 +12,11 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) +// Errors related to ERC20 calls and FunToken mappings. +const ( + ErrOwnable = "Ownable: caller is not the owner" +) + const ( codeErrInvalidState = uint32(iota) + 2 // NOTE: code 1 is reserved for internal errors codeErrZeroAddress diff --git a/x/evm/evm.pb.go b/x/evm/evm.pb.go index 8607ee45b..ed6714765 100644 --- a/x/evm/evm.pb.go +++ b/x/evm/evm.pb.go @@ -4,6 +4,7 @@ package evm import ( + cosmossdk_io_math "cosmossdk.io/math" fmt "fmt" github_com_NibiruChain_nibiru_eth "github.com/NibiruChain/nibiru/eth" _ "github.com/cosmos/gogoproto/gogoproto" @@ -91,9 +92,11 @@ type Params struct { // evm_denom represents the token denomination used to run the EVM state // transitions. EvmDenom string `protobuf:"bytes,1,opt,name=evm_denom,json=evmDenom,proto3" json:"evm_denom,omitempty" yaml:"evm_denom"` - // enable_create toggles state transitions that use the vm.Create function + // enable_create: Enables contract creation by allowing for state transitions + // that use the "vm.Create" function EnableCreate bool `protobuf:"varint,2,opt,name=enable_create,json=enableCreate,proto3" json:"enable_create,omitempty" yaml:"enable_create"` - // enable_call toggles state transitions that use the vm.Call function + // enable_call: Enables contract calls by allowing for state transitions that + // use the "vm.Call" function EnableCall bool `protobuf:"varint,3,opt,name=enable_call,json=enableCall,proto3" json:"enable_call,omitempty" yaml:"enable_call"` // extra_eips defines the additional EIPs for the vm.Config ExtraEIPs []int64 `protobuf:"varint,4,rep,packed,name=extra_eips,json=extraEips,proto3" json:"extra_eips,omitempty" yaml:"extra_eips"` @@ -105,6 +108,9 @@ type Params struct { ActivePrecompiles []string `protobuf:"bytes,7,rep,name=active_precompiles,json=activePrecompiles,proto3" json:"active_precompiles,omitempty"` // evm_channels is the list of channel identifiers from EVM compatible chains EVMChannels []string `protobuf:"bytes,8,rep,name=evm_channels,json=evmChannels,proto3" json:"evm_channels,omitempty"` + // Fee deducted and burned when calling "CreateFunToken" in units of + // "evm_denom". + CreateFuntokenFee cosmossdk_io_math.Int `protobuf:"bytes,9,opt,name=create_funtoken_fee,json=createFuntokenFee,proto3,customtype=cosmossdk.io/math.Int" json:"create_funtoken_fee"` } func (m *Params) Reset() { *m = Params{} } @@ -661,78 +667,81 @@ func init() { func init() { proto.RegisterFile("eth/evm/v1/evm.proto", fileDescriptor_98abbdadb327b7d0) } var fileDescriptor_98abbdadb327b7d0 = []byte{ - // 1135 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0xcf, 0x6e, 0xdb, 0xc6, - 0x13, 0xb6, 0x2c, 0xca, 0xa2, 0x56, 0x72, 0xa4, 0x6c, 0xf4, 0xcb, 0x8f, 0x4d, 0x50, 0xd3, 0x60, - 0x2e, 0x0e, 0x90, 0x5a, 0xb5, 0x8b, 0xa2, 0x40, 0x8a, 0x14, 0x30, 0x1d, 0x07, 0x89, 0xeb, 0x04, - 0xc6, 0xc6, 0xe9, 0xa1, 0x17, 0x62, 0x45, 0x4e, 0x28, 0xd6, 0x24, 0x57, 0xd8, 0x5d, 0xaa, 0xf4, - 0x03, 0x14, 0xe8, 0xb1, 0xd7, 0xde, 0x72, 0xea, 0xb3, 0x04, 0x3d, 0xe5, 0x58, 0xf4, 0x40, 0x14, - 0xce, 0xa5, 0xd5, 0xd1, 0x4f, 0x50, 0xec, 0x2e, 0x65, 0xd9, 0x29, 0x90, 0x13, 0xe7, 0xfb, 0xbe, - 0x9d, 0x3f, 0x9c, 0x19, 0x2e, 0xd1, 0x10, 0xe4, 0x64, 0x04, 0xb3, 0x6c, 0x34, 0xdb, 0x51, 0x8f, - 0xed, 0x29, 0x67, 0x92, 0x61, 0x04, 0x72, 0xb2, 0xad, 0xe0, 0x6c, 0xe7, 0xce, 0x30, 0x66, 0x31, - 0xd3, 0xf4, 0x48, 0x59, 0xe6, 0x84, 0xf7, 0x5b, 0x03, 0xd9, 0x4f, 0x8a, 0xfc, 0x84, 0x9d, 0x42, - 0x8e, 0x8f, 0x11, 0x02, 0x1e, 0xee, 0x7e, 0x1e, 0xd0, 0x28, 0xe2, 0x4e, 0x63, 0xb3, 0xb1, 0xd5, - 0xf1, 0x77, 0xde, 0x56, 0xee, 0xca, 0x9f, 0x95, 0x7b, 0x3f, 0x4e, 0xe4, 0xa4, 0x18, 0x6f, 0x87, - 0x2c, 0x1b, 0xbd, 0x48, 0xc6, 0x09, 0x2f, 0xf6, 0x27, 0x34, 0xc9, 0x47, 0xb9, 0xb6, 0x47, 0x2a, - 0xd1, 0x53, 0x28, 0xf7, 0xa2, 0x88, 0x93, 0x8e, 0x0e, 0xa2, 0x4c, 0xfc, 0x29, 0x42, 0x63, 0x9a, - 0x9f, 0x06, 0x11, 0xe4, 0x2c, 0x73, 0x56, 0x55, 0x44, 0xd2, 0x51, 0xcc, 0x63, 0x45, 0xe0, 0xfb, - 0xe8, 0x66, 0x22, 0x82, 0x8c, 0x46, 0x10, 0xbc, 0xe6, 0x2c, 0x0b, 0x42, 0x96, 0xe4, 0x4e, 0x73, - 0xb3, 0xb1, 0x65, 0x93, 0x1b, 0x89, 0x78, 0x4e, 0x23, 0x78, 0xc2, 0x59, 0xb6, 0xcf, 0x92, 0xdc, - 0xfb, 0xb5, 0x89, 0xd6, 0x8e, 0x29, 0xa7, 0x99, 0xc0, 0x3b, 0xa8, 0x03, 0xb3, 0xac, 0x8e, 0x69, - 0xaa, 0x1c, 0x5e, 0x54, 0xee, 0xe0, 0x8c, 0x66, 0xe9, 0x43, 0xef, 0x52, 0xf2, 0x88, 0x0d, 0xb3, - 0xcc, 0x24, 0x7a, 0x84, 0xd6, 0x21, 0xa7, 0xe3, 0x14, 0x82, 0x90, 0x03, 0x95, 0xa0, 0x4b, 0xb1, - 0x7d, 0xe7, 0xa2, 0x72, 0x87, 0xb5, 0xdb, 0x55, 0xd9, 0x23, 0x3d, 0x83, 0xf7, 0x35, 0xc4, 0x5f, - 0xa1, 0xee, 0x42, 0xa7, 0x69, 0x6a, 0x2a, 0xf4, 0x6f, 0x5f, 0x54, 0x2e, 0xbe, 0xee, 0x4c, 0xd3, - 0xd4, 0x23, 0xa8, 0x76, 0xa5, 0x69, 0x8a, 0xf7, 0x10, 0x82, 0x52, 0x72, 0x1a, 0x40, 0x32, 0x15, - 0x8e, 0xb5, 0xd9, 0xdc, 0x6a, 0xfa, 0xde, 0x79, 0xe5, 0x76, 0x0e, 0x14, 0x7b, 0xf0, 0xec, 0x58, - 0x5c, 0x54, 0xee, 0xcd, 0x3a, 0xc8, 0xe5, 0x41, 0x8f, 0x74, 0x34, 0x38, 0x48, 0xa6, 0x02, 0xef, - 0xa2, 0xff, 0xd1, 0x34, 0x65, 0x3f, 0x06, 0x45, 0xae, 0x46, 0x06, 0xa1, 0x84, 0x28, 0x90, 0xa5, - 0x70, 0xd6, 0x74, 0x9f, 0x6e, 0x69, 0xf1, 0xd5, 0x52, 0x3b, 0x29, 0x05, 0xfe, 0x0c, 0x61, 0x1a, - 0xca, 0x64, 0x06, 0xc1, 0x94, 0x43, 0xc8, 0xb2, 0x69, 0x92, 0x82, 0x70, 0xda, 0x9b, 0xcd, 0xad, - 0x0e, 0xb9, 0x69, 0x94, 0xe3, 0xa5, 0x80, 0x77, 0x51, 0x4f, 0x75, 0x2d, 0x9c, 0xd0, 0x3c, 0x87, - 0x54, 0x38, 0xb6, 0x3a, 0xe8, 0xf7, 0xcf, 0x2b, 0xb7, 0x7b, 0xf0, 0xdd, 0xf3, 0xfd, 0x9a, 0x26, - 0x5d, 0x98, 0x65, 0x0b, 0xf0, 0xd0, 0xfa, 0xfb, 0x8d, 0xdb, 0x38, 0xb4, 0xec, 0xd6, 0x60, 0xcd, - 0x1b, 0xa1, 0xd6, 0x4b, 0xa9, 0xfa, 0x34, 0x40, 0xcd, 0x53, 0x38, 0x33, 0x33, 0x21, 0xca, 0xc4, - 0x43, 0xd4, 0x9a, 0xd1, 0xb4, 0x80, 0x7a, 0xf6, 0x06, 0x78, 0x87, 0xa8, 0x7f, 0xc2, 0x69, 0x2e, - 0x54, 0x29, 0x2c, 0x3f, 0x62, 0xb1, 0xc0, 0x18, 0x59, 0x13, 0x2a, 0x26, 0xb5, 0xaf, 0xb6, 0xf1, - 0x3d, 0x64, 0xa5, 0x2c, 0x16, 0xce, 0xea, 0x66, 0x73, 0xab, 0xbb, 0xdb, 0xdf, 0x5e, 0x6e, 0xf3, - 0xf6, 0x11, 0x8b, 0x89, 0x16, 0xbd, 0xdf, 0x57, 0x51, 0xf3, 0x88, 0xc5, 0xd8, 0x41, 0x6d, 0xb5, - 0xb6, 0x20, 0x44, 0x1d, 0x63, 0x01, 0xf1, 0x6d, 0xb4, 0x26, 0xd9, 0x34, 0x09, 0x4d, 0xa0, 0x0e, - 0xa9, 0x91, 0x4a, 0x19, 0x51, 0x49, 0xf5, 0x38, 0x7b, 0x44, 0xdb, 0xaa, 0x15, 0xe3, 0x94, 0x85, - 0xa7, 0x41, 0x5e, 0x64, 0x63, 0xe0, 0x8e, 0xb5, 0xd9, 0xd8, 0xb2, 0xfc, 0xfe, 0xbc, 0x72, 0xbb, - 0x9a, 0x7f, 0xa1, 0x69, 0x72, 0x15, 0xe0, 0x07, 0xa8, 0x2d, 0xcb, 0x40, 0x57, 0xdf, 0xd2, 0xdb, - 0x78, 0x6b, 0x5e, 0xb9, 0x7d, 0xb9, 0x7c, 0xc1, 0xa7, 0x54, 0x4c, 0xc8, 0x9a, 0x2c, 0xd5, 0x13, - 0x8f, 0x90, 0x2d, 0xcb, 0x20, 0xc9, 0x23, 0x28, 0xf5, 0x08, 0x2d, 0x7f, 0x38, 0xaf, 0xdc, 0xc1, - 0x95, 0xe3, 0xcf, 0x94, 0x46, 0xda, 0xb2, 0xd4, 0x06, 0x7e, 0x80, 0x90, 0x29, 0x49, 0x67, 0x68, - 0xeb, 0x0c, 0xeb, 0xf3, 0xca, 0xed, 0x68, 0x56, 0xc7, 0x5e, 0x9a, 0xd8, 0x43, 0x2d, 0x13, 0xdb, - 0xd6, 0xb1, 0x7b, 0xf3, 0xca, 0xb5, 0x53, 0x16, 0x9b, 0x98, 0x46, 0x52, 0xad, 0xe2, 0x90, 0xb1, - 0x19, 0x44, 0x4e, 0x47, 0x2f, 0xd1, 0x02, 0x7a, 0x3f, 0xad, 0x22, 0xfb, 0xa4, 0x24, 0x20, 0x8a, - 0x54, 0xe2, 0x27, 0x68, 0x10, 0xb2, 0x5c, 0x72, 0x1a, 0xca, 0xe0, 0x5a, 0x6b, 0xfd, 0xbb, 0x17, - 0x95, 0xfb, 0x7f, 0xb3, 0xb5, 0x1f, 0x9e, 0xf0, 0x48, 0x7f, 0x41, 0xed, 0xd5, 0xfd, 0x1f, 0xa2, - 0xd6, 0x38, 0x65, 0xf5, 0xf7, 0xdf, 0x23, 0x06, 0xe0, 0x23, 0xdd, 0x35, 0x3d, 0x5f, 0x35, 0x80, - 0xee, 0xee, 0xdd, 0xab, 0xf3, 0xfd, 0x60, 0x3d, 0xfc, 0xdb, 0xea, 0x1a, 0xba, 0xa8, 0xdc, 0x1b, - 0x26, 0x6b, 0xed, 0xe9, 0xa9, 0xae, 0xea, 0xf5, 0x19, 0xa0, 0x26, 0x07, 0xa9, 0xc7, 0xd5, 0x23, - 0xca, 0xc4, 0x77, 0x90, 0xcd, 0x61, 0x06, 0x5c, 0x42, 0xa4, 0xc7, 0x62, 0x93, 0x4b, 0x8c, 0x3f, - 0x41, 0x76, 0x4c, 0x45, 0x50, 0x08, 0x88, 0xcc, 0x0c, 0x48, 0x3b, 0xa6, 0xe2, 0x95, 0x80, 0xe8, - 0xa1, 0xf5, 0xf3, 0x1b, 0x77, 0xc5, 0xa3, 0xa8, 0xbb, 0x17, 0x86, 0x20, 0xc4, 0x49, 0x31, 0x4d, - 0xe1, 0x23, 0xbb, 0xb5, 0x8b, 0x7a, 0x42, 0x32, 0x4e, 0x63, 0x08, 0x4e, 0xe1, 0xac, 0xde, 0x30, - 0xb3, 0x2f, 0x35, 0xff, 0x2d, 0x9c, 0x09, 0x72, 0x15, 0xd4, 0x29, 0xfe, 0x69, 0xa2, 0xee, 0x09, - 0xa7, 0x21, 0xec, 0xb3, 0xfc, 0x75, 0x12, 0xeb, 0x2d, 0x55, 0xb0, 0xbe, 0x78, 0x49, 0x8d, 0x54, - 0x6e, 0x99, 0x64, 0xc0, 0x0a, 0x59, 0x7f, 0x43, 0x0b, 0xa8, 0x3c, 0x38, 0x40, 0x09, 0xa1, 0x6e, - 0xa0, 0x45, 0x6a, 0x84, 0xbf, 0x44, 0xeb, 0x51, 0x22, 0xf4, 0x8d, 0x24, 0x24, 0x0d, 0x4f, 0xcd, - 0xeb, 0xfb, 0x83, 0x79, 0xe5, 0xf6, 0x6a, 0xe1, 0xa5, 0xe2, 0xc9, 0x35, 0x84, 0xbf, 0x46, 0xfd, - 0xa5, 0x9b, 0xae, 0xd6, 0x5c, 0x31, 0x3e, 0x9e, 0x57, 0xee, 0x8d, 0xcb, 0xa3, 0x5a, 0x21, 0x1f, - 0x60, 0x35, 0xe3, 0x08, 0xc6, 0x45, 0xac, 0xd7, 0xce, 0x26, 0x06, 0x28, 0x36, 0x4d, 0xb2, 0x44, - 0xea, 0x35, 0x6b, 0x11, 0x03, 0x54, 0x7d, 0xf5, 0x85, 0x99, 0x41, 0xc6, 0xf8, 0x99, 0xd3, 0x5d, - 0xd6, 0x67, 0x84, 0xe7, 0x9a, 0x27, 0xd7, 0x10, 0xf6, 0x11, 0xae, 0xdd, 0x38, 0xc8, 0x82, 0xe7, - 0x81, 0xfe, 0x78, 0x7b, 0xda, 0x57, 0x7f, 0x42, 0x46, 0x25, 0x5a, 0x7c, 0x4c, 0x25, 0x25, 0xff, - 0x61, 0xf0, 0x37, 0x08, 0x9b, 0xb6, 0x06, 0x3f, 0x08, 0x96, 0x07, 0xa1, 0x6e, 0xbd, 0xb3, 0xae, - 0x97, 0x5a, 0xe7, 0x37, 0xaa, 0x19, 0x09, 0x19, 0x18, 0x74, 0x28, 0x58, 0x6e, 0x98, 0x43, 0xcb, - 0xb6, 0x06, 0xad, 0x43, 0xcb, 0x6e, 0x0f, 0xec, 0x43, 0xcb, 0x46, 0x83, 0xee, 0x65, 0x23, 0xea, - 0x77, 0x21, 0xb7, 0x16, 0xf8, 0x4a, 0x91, 0xfe, 0xa3, 0xb7, 0xe7, 0x1b, 0x8d, 0x77, 0xe7, 0x1b, - 0x8d, 0xbf, 0xce, 0x37, 0x1a, 0xbf, 0xbc, 0xdf, 0x58, 0x79, 0xf7, 0x7e, 0x63, 0xe5, 0x8f, 0xf7, - 0x1b, 0x2b, 0xdf, 0xdf, 0xfb, 0xf8, 0x6f, 0xb5, 0x54, 0x3f, 0xf3, 0xf1, 0x9a, 0xfe, 0x57, 0x7f, - 0xf1, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x94, 0x04, 0x70, 0x58, 0xe5, 0x07, 0x00, 0x00, + // 1181 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x56, 0xcf, 0x6e, 0xdb, 0xc6, + 0x13, 0xb6, 0x2c, 0xca, 0xa2, 0x56, 0x72, 0x24, 0xaf, 0x95, 0xfc, 0xf8, 0x4b, 0x10, 0xd3, 0x60, + 0x2e, 0x0e, 0x90, 0x4a, 0xb5, 0x8b, 0xa2, 0x40, 0x8a, 0x14, 0x30, 0x1d, 0x1b, 0x89, 0x6b, 0x07, + 0xc6, 0xc6, 0xe9, 0xa1, 0x17, 0x62, 0x45, 0x8e, 0x29, 0x56, 0x24, 0x57, 0xe0, 0x2e, 0x55, 0xf9, + 0x01, 0x0a, 0xf4, 0xd8, 0x47, 0xc8, 0xa9, 0xcf, 0x12, 0xf4, 0x94, 0x63, 0xd1, 0x03, 0x51, 0x38, + 0x97, 0x54, 0x47, 0x3f, 0x41, 0xb1, 0xbb, 0x94, 0x25, 0xa7, 0x40, 0x4e, 0x9e, 0x6f, 0xbe, 0x9d, + 0x3f, 0x9c, 0xf9, 0x76, 0x65, 0xd4, 0x05, 0x31, 0xec, 0xc3, 0x24, 0xe9, 0x4f, 0x76, 0xe5, 0x9f, + 0xde, 0x38, 0x63, 0x82, 0x61, 0x04, 0x62, 0xd8, 0x93, 0x70, 0xb2, 0x7b, 0xbf, 0x1b, 0xb2, 0x90, + 0x29, 0x77, 0x5f, 0x5a, 0xfa, 0x84, 0xf3, 0x7b, 0x05, 0x99, 0x47, 0x79, 0x7a, 0xce, 0x46, 0x90, + 0xe2, 0x33, 0x84, 0x20, 0xf3, 0xf7, 0xbe, 0xf4, 0x68, 0x10, 0x64, 0x56, 0x65, 0xbb, 0xb2, 0xd3, + 0x70, 0x77, 0xdf, 0x15, 0xf6, 0xca, 0x5f, 0x85, 0xfd, 0x38, 0x8c, 0xc4, 0x30, 0x1f, 0xf4, 0x7c, + 0x96, 0xf4, 0x5f, 0x45, 0x83, 0x28, 0xcb, 0x0f, 0x86, 0x34, 0x4a, 0xfb, 0xa9, 0xb2, 0xfb, 0xb2, + 0xd0, 0x0b, 0x98, 0xee, 0x07, 0x41, 0x46, 0x1a, 0x2a, 0x89, 0x34, 0xf1, 0x43, 0x84, 0x06, 0x34, + 0x1d, 0x79, 0x01, 0xa4, 0x2c, 0xb1, 0x56, 0x65, 0x46, 0xd2, 0x90, 0x9e, 0xe7, 0xd2, 0x81, 0x1f, + 0xa3, 0x8d, 0x88, 0x7b, 0x09, 0x0d, 0xc0, 0xbb, 0xc8, 0x58, 0xe2, 0xf9, 0x2c, 0x4a, 0xad, 0xea, + 0x76, 0x65, 0xc7, 0x24, 0x77, 0x22, 0x7e, 0x4a, 0x03, 0x38, 0xca, 0x58, 0x72, 0xc0, 0xa2, 0xd4, + 0xf9, 0x58, 0x45, 0x6b, 0x67, 0x34, 0xa3, 0x09, 0xc7, 0xbb, 0xa8, 0x01, 0x93, 0xa4, 0xcc, 0xa9, + 0xbb, 0xec, 0x5e, 0x17, 0x76, 0xe7, 0x92, 0x26, 0xf1, 0x53, 0xe7, 0x86, 0x72, 0x88, 0x09, 0x93, + 0x44, 0x17, 0x7a, 0x86, 0xd6, 0x21, 0xa5, 0x83, 0x18, 0x3c, 0x3f, 0x03, 0x2a, 0x40, 0xb5, 0x62, + 0xba, 0xd6, 0x75, 0x61, 0x77, 0xcb, 0xb0, 0x65, 0xda, 0x21, 0x2d, 0x8d, 0x0f, 0x14, 0xc4, 0xdf, + 0xa0, 0xe6, 0x9c, 0xa7, 0x71, 0xac, 0x3b, 0x74, 0xef, 0x5d, 0x17, 0x36, 0xbe, 0x1d, 0x4c, 0xe3, + 0xd8, 0x21, 0xa8, 0x0c, 0xa5, 0x71, 0x8c, 0xf7, 0x11, 0x82, 0xa9, 0xc8, 0xa8, 0x07, 0xd1, 0x98, + 0x5b, 0xc6, 0x76, 0x75, 0xa7, 0xea, 0x3a, 0x57, 0x85, 0xdd, 0x38, 0x94, 0xde, 0xc3, 0x97, 0x67, + 0xfc, 0xba, 0xb0, 0x37, 0xca, 0x24, 0x37, 0x07, 0x1d, 0xd2, 0x50, 0xe0, 0x30, 0x1a, 0x73, 0xbc, + 0x87, 0xee, 0xd2, 0x38, 0x66, 0x3f, 0x7b, 0x79, 0x2a, 0x57, 0x06, 0xbe, 0x80, 0xc0, 0x13, 0x53, + 0x6e, 0xad, 0xa9, 0x39, 0x6d, 0x2a, 0xf2, 0xcd, 0x82, 0x3b, 0x9f, 0x72, 0xfc, 0x05, 0xc2, 0xd4, + 0x17, 0xd1, 0x04, 0xbc, 0x71, 0x06, 0x3e, 0x4b, 0xc6, 0x51, 0x0c, 0xdc, 0xaa, 0x6f, 0x57, 0x77, + 0x1a, 0x64, 0x43, 0x33, 0x67, 0x0b, 0x02, 0xef, 0xa1, 0x96, 0x9c, 0x9a, 0x3f, 0xa4, 0x69, 0x0a, + 0x31, 0xb7, 0x4c, 0x79, 0xd0, 0x6d, 0x5f, 0x15, 0x76, 0xf3, 0xf0, 0x87, 0xd3, 0x83, 0xd2, 0x4d, + 0x9a, 0x30, 0x49, 0xe6, 0x00, 0x9f, 0xa2, 0x4d, 0x3d, 0x2b, 0xef, 0x22, 0x4f, 0x85, 0x94, 0x8f, + 0x77, 0x01, 0x60, 0x35, 0xd4, 0x3a, 0x1e, 0x96, 0xa2, 0xb9, 0xeb, 0x33, 0x9e, 0x30, 0xce, 0x83, + 0x51, 0x2f, 0x62, 0xfd, 0x84, 0x8a, 0x61, 0xef, 0x65, 0x2a, 0xc8, 0x86, 0x8e, 0x3c, 0x2a, 0x03, + 0x8f, 0x00, 0x9e, 0x1a, 0x1f, 0xdf, 0xda, 0x95, 0x63, 0xc3, 0xac, 0x75, 0xd6, 0x9c, 0x3e, 0xaa, + 0xbd, 0x16, 0x72, 0xec, 0x1d, 0x54, 0x1d, 0xc1, 0xa5, 0x5e, 0x31, 0x91, 0x26, 0xee, 0xa2, 0xda, + 0x84, 0xc6, 0x39, 0x94, 0x52, 0xd2, 0xc0, 0x39, 0x46, 0xed, 0xf3, 0x8c, 0xa6, 0x5c, 0x7e, 0x19, + 0x4b, 0x4f, 0x58, 0xc8, 0x31, 0x46, 0xc6, 0x90, 0xf2, 0x61, 0x19, 0xab, 0x6c, 0xfc, 0x08, 0x19, + 0x31, 0x0b, 0xb9, 0xb5, 0xba, 0x5d, 0xdd, 0x69, 0xee, 0xb5, 0x7b, 0x8b, 0xcb, 0xd1, 0x3b, 0x61, + 0x21, 0x51, 0xa4, 0xf3, 0xc7, 0x2a, 0xaa, 0x9e, 0xb0, 0x10, 0x5b, 0xa8, 0x2e, 0x6f, 0x01, 0x70, + 0x5e, 0xe6, 0x98, 0x43, 0x7c, 0x0f, 0xad, 0x09, 0x36, 0x8e, 0x7c, 0x9d, 0xa8, 0x41, 0x4a, 0x24, + 0x4b, 0x06, 0x54, 0x50, 0xa5, 0x8e, 0x16, 0x51, 0xb6, 0x9c, 0xec, 0x20, 0x66, 0xfe, 0xc8, 0x4b, + 0xf3, 0x64, 0x00, 0x99, 0x65, 0x6c, 0x57, 0x76, 0x0c, 0xb7, 0x3d, 0x2b, 0xec, 0xa6, 0xf2, 0xbf, + 0x52, 0x6e, 0xb2, 0x0c, 0xf0, 0x13, 0x54, 0x17, 0x53, 0x4f, 0x75, 0x5f, 0x53, 0xd3, 0xdc, 0x9c, + 0x15, 0x76, 0x5b, 0x2c, 0x3e, 0xf0, 0x05, 0xe5, 0x43, 0xb2, 0x26, 0xa6, 0xf2, 0x2f, 0xee, 0x23, + 0x53, 0x4c, 0xbd, 0x28, 0x0d, 0x60, 0xaa, 0x14, 0x61, 0xb8, 0xdd, 0x59, 0x61, 0x77, 0x96, 0x8e, + 0xbf, 0x94, 0x1c, 0xa9, 0x8b, 0xa9, 0x32, 0xf0, 0x13, 0x84, 0x74, 0x4b, 0xaa, 0x42, 0x5d, 0x55, + 0x58, 0x9f, 0x15, 0x76, 0x43, 0x79, 0x55, 0xee, 0x85, 0x89, 0x1d, 0x54, 0xd3, 0xb9, 0x4d, 0x95, + 0xbb, 0x35, 0x2b, 0x6c, 0x33, 0x66, 0xa1, 0xce, 0xa9, 0x29, 0x39, 0xaa, 0x0c, 0x12, 0x36, 0x81, + 0x40, 0xad, 0xdf, 0x24, 0x73, 0xe8, 0xfc, 0xb2, 0x8a, 0xcc, 0xf3, 0x29, 0x01, 0x9e, 0xc7, 0x02, + 0x1f, 0xa1, 0x8e, 0xcf, 0x52, 0x91, 0x51, 0x5f, 0x78, 0xb7, 0x46, 0xeb, 0x3e, 0xb8, 0x2e, 0xec, + 0xff, 0xe9, 0x4b, 0xf0, 0xe9, 0x09, 0x87, 0xb4, 0xe7, 0xae, 0xfd, 0x72, 0xfe, 0x5d, 0x54, 0x1b, + 0xc4, 0xac, 0x7c, 0x4e, 0x5a, 0x44, 0x03, 0x7c, 0xa2, 0xa6, 0xa6, 0xf6, 0x2b, 0x17, 0xd0, 0xdc, + 0x7b, 0xb0, 0xbc, 0xdf, 0x4f, 0xe4, 0xe1, 0xde, 0x93, 0x02, 0xbd, 0x2e, 0xec, 0x3b, 0xba, 0x6a, + 0x19, 0xe9, 0xc8, 0xa9, 0x2a, 0xf9, 0x74, 0x50, 0x35, 0x03, 0xa1, 0xd6, 0xd5, 0x22, 0xd2, 0xc4, + 0xf7, 0x91, 0x99, 0xc1, 0x04, 0x32, 0x01, 0x81, 0x5a, 0x8b, 0x49, 0x6e, 0x30, 0xfe, 0x3f, 0x32, + 0x43, 0xca, 0xbd, 0x9c, 0x43, 0xa0, 0x77, 0x40, 0xea, 0x21, 0xe5, 0x6f, 0x38, 0x04, 0x4f, 0x8d, + 0x5f, 0xdf, 0xda, 0x2b, 0x0e, 0x45, 0xcd, 0x7d, 0xdf, 0x07, 0xce, 0xcf, 0xf3, 0x71, 0x0c, 0x9f, + 0xd1, 0xd6, 0x1e, 0x6a, 0x71, 0xc1, 0x32, 0x1a, 0x82, 0x37, 0x82, 0xcb, 0x52, 0x61, 0x5a, 0x2f, + 0xa5, 0xff, 0x7b, 0xb8, 0xe4, 0x64, 0x19, 0x94, 0x25, 0xfe, 0xa9, 0xa2, 0xe6, 0x79, 0x46, 0x7d, + 0x38, 0x60, 0xe9, 0x45, 0x14, 0x2a, 0x95, 0x4a, 0x58, 0xbe, 0xe3, 0xa4, 0x44, 0xb2, 0xb6, 0x88, + 0x12, 0x60, 0xb9, 0x28, 0xef, 0xd0, 0x1c, 0xca, 0x88, 0x0c, 0x60, 0x0a, 0xbe, 0x1a, 0xa0, 0x41, + 0x4a, 0x84, 0xbf, 0x46, 0xeb, 0x41, 0xc4, 0xd5, 0x03, 0xc7, 0x05, 0xf5, 0x47, 0xfa, 0xf3, 0xdd, + 0xce, 0xac, 0xb0, 0x5b, 0x25, 0xf1, 0x5a, 0xfa, 0xc9, 0x2d, 0x84, 0xbf, 0x45, 0xed, 0x45, 0x98, + 0xea, 0x56, 0xbf, 0x58, 0x2e, 0x9e, 0x15, 0xf6, 0x9d, 0x9b, 0xa3, 0x8a, 0x21, 0x9f, 0x60, 0xb9, + 0xe3, 0x00, 0x06, 0x79, 0xa8, 0x64, 0x67, 0x12, 0x0d, 0xa4, 0x37, 0x8e, 0x92, 0x48, 0x28, 0x99, + 0xd5, 0x88, 0x06, 0xb2, 0xbf, 0xf2, 0xfd, 0x4d, 0x20, 0x61, 0xd9, 0xa5, 0xd5, 0x5c, 0xf4, 0xa7, + 0x89, 0x53, 0xe5, 0x27, 0xb7, 0x10, 0x76, 0x11, 0x2e, 0xc3, 0x32, 0x10, 0x79, 0x96, 0x7a, 0xea, + 0xf2, 0xb6, 0x54, 0xac, 0xba, 0x42, 0x9a, 0x25, 0x8a, 0x7c, 0x4e, 0x05, 0x25, 0xff, 0xf1, 0xe0, + 0xef, 0x10, 0xd6, 0x63, 0xf5, 0x7e, 0xe2, 0x2c, 0xf5, 0x7c, 0x35, 0x7a, 0x6b, 0x5d, 0x89, 0x5a, + 0xd5, 0xd7, 0xac, 0x5e, 0x09, 0xe9, 0x68, 0x74, 0xcc, 0x59, 0xaa, 0x3d, 0xc7, 0x86, 0x69, 0x74, + 0x6a, 0xc7, 0x86, 0x59, 0xef, 0x98, 0xc7, 0x86, 0x89, 0x3a, 0xcd, 0x9b, 0x41, 0x94, 0xdf, 0x42, + 0x36, 0xe7, 0x78, 0xa9, 0x49, 0xf7, 0xd9, 0xbb, 0xab, 0xad, 0xca, 0xfb, 0xab, 0xad, 0xca, 0xdf, + 0x57, 0x5b, 0x95, 0xdf, 0x3e, 0x6c, 0xad, 0xbc, 0xff, 0xb0, 0xb5, 0xf2, 0xe7, 0x87, 0xad, 0x95, + 0x1f, 0x1f, 0x7d, 0xfe, 0x57, 0x7a, 0x2a, 0xff, 0x37, 0x18, 0xac, 0xa9, 0x9f, 0xfe, 0xaf, 0xfe, + 0x0d, 0x00, 0x00, 0xff, 0xff, 0x12, 0x25, 0x13, 0xb4, 0x34, 0x08, 0x00, 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -790,6 +799,9 @@ func (this *Params) Equal(that interface{}) bool { return false } } + if !this.CreateFuntokenFee.Equal(that1.CreateFuntokenFee) { + return false + } return true } func (m *FunToken) Marshal() (dAtA []byte, err error) { @@ -862,6 +874,16 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.CreateFuntokenFee.Size() + i -= size + if _, err := m.CreateFuntokenFee.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvm(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a if len(m.EVMChannels) > 0 { for iNdEx := len(m.EVMChannels) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.EVMChannels[iNdEx]) @@ -1384,6 +1406,8 @@ func (m *Params) Size() (n int) { n += 1 + l + sovEvm(uint64(l)) } } + l = m.CreateFuntokenFee.Size() + n += 1 + l + sovEvm(uint64(l)) return n } @@ -1959,6 +1983,40 @@ func (m *Params) Unmarshal(dAtA []byte) error { } m.EVMChannels = append(m.EVMChannels, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CreateFuntokenFee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvm + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.CreateFuntokenFee.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvm(dAtA[iNdEx:]) diff --git a/x/evm/evmtest/erc20.go b/x/evm/evmtest/erc20.go index 9709c914c..d072fcec2 100644 --- a/x/evm/evmtest/erc20.go +++ b/x/evm/evmtest/erc20.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/NibiruChain/nibiru/x/common/testutil/testapp" "github.com/NibiruChain/nibiru/x/evm" ) @@ -23,7 +24,7 @@ func DoEthTx( func AssertERC20BalanceEqual( t *testing.T, - deps *TestDeps, + deps TestDeps, contract, account gethcommon.Address, balance *big.Int, ) { @@ -55,6 +56,15 @@ func CreateFunTokenForBankCoin( } deps.Chain.BankKeeper.SetDenomMetaData(deps.Ctx, bankMetadata) + // Give the sender funds for the fee + err := testapp.FundAccount( + deps.Chain.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.K.FeeForCreateFunToken(deps.Ctx), + ) + s.Require().NoError(err) + s.T().Log("happy: CreateFunToken for the bank coin") createFuntokenResp, err := deps.K.CreateFunToken( deps.GoCtx(), diff --git a/x/evm/evmtest/eth_test.go b/x/evm/evmtest/eth_test.go index 774b2d3d7..00ebc7d0d 100644 --- a/x/evm/evmtest/eth_test.go +++ b/x/evm/evmtest/eth_test.go @@ -40,7 +40,7 @@ func (s *Suite) TestERC20Helpers() { erc20Contract := funtoken.Erc20Addr.ToAddr() evmtest.AssertERC20BalanceEqual( - s.T(), &deps, + s.T(), deps, erc20Contract, deps.Sender.EthAddr, big.NewInt(0), diff --git a/x/evm/keeper/erc20.go b/x/evm/keeper/erc20.go index 20bd0ef9a..cae62efcc 100644 --- a/x/evm/keeper/erc20.go +++ b/x/evm/keeper/erc20.go @@ -6,161 +6,109 @@ import ( "fmt" "math/big" - "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - bank "github.com/cosmos/cosmos-sdk/x/bank/types" gethabi "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" gethcore "github.com/ethereum/go-ethereum/core/types" serverconfig "github.com/NibiruChain/nibiru/app/server/config" - "github.com/NibiruChain/nibiru/eth" - "github.com/NibiruChain/nibiru/x/common" "github.com/NibiruChain/nibiru/x/evm" "github.com/NibiruChain/nibiru/x/evm/embeds" "github.com/NibiruChain/nibiru/x/evm/statedb" ) -// FindERC20Metadata retrieves the metadata of an ERC20 token. -// -// Parameters: -// - ctx: The SDK context for the transaction. -// - contract: The Ethereum address of the ERC20 contract. -// -// Returns: -// - info: ERC20Metadata containing name, symbol, and decimals. -// - err: An error if metadata retrieval fails. -func (k Keeper) FindERC20Metadata( - ctx sdk.Context, - contract gethcommon.Address, -) (info ERC20Metadata, err error) { - var abi gethabi.ABI = embeds.Contract_ERC20Minter.ABI - - errs := []error{} - - // Load name, symbol, decimals - name, err := k.LoadERC20Name(ctx, abi, contract) - errs = append(errs, err) - symbol, err := k.LoadERC20Symbol(ctx, abi, contract) - errs = append(errs, err) - decimals, err := k.LoadERC20Decimals(ctx, abi, contract) - errs = append(errs, err) - - err = common.CombineErrors(errs...) - if err != nil { - return info, errors.Wrap(err, "failed to \"FindERC20Metadata\"") +// ERC20 returns a mutable reference to the keeper with an ERC20 contract ABI and +// Go functions corresponding to contract calls in the ERC20 standard like "mint" +// and "transfer" in the ERC20 standard. +func (k Keeper) ERC20() erc20Calls { + return erc20Calls{ + Keeper: &k, + ABI: embeds.Contract_ERC20Minter.ABI, } - - return ERC20Metadata{ - Name: name, - Symbol: symbol, - Decimals: decimals, - }, nil } -type ERC20Metadata struct { - Name string - Symbol string - Decimals uint8 +type erc20Calls struct { + *Keeper + ABI gethabi.ABI } -type ( - ERC20String struct{ Value string } - // ERC20Uint8: Unpacking type for "uint8" from Solidity. This is only used in - // the "ERC20.decimals" function. - ERC20Uint8 struct{ Value uint8 } - ERC20Bool struct{ Value bool } - ERC20BigInt struct{ Value *big.Int } -) - -// CreateFunTokenFromERC20 creates a new FunToken mapping from an existing ERC20 token. -// -// This function performs the following steps: -// 1. Checks if the ERC20 token is already registered as a FunToken. -// 2. Retrieves the metadata of the existing ERC20 token. -// 3. Verifies that the corresponding bank coin denom is not already registered. -// 4. Sets the bank coin denom metadata in the state. -// 5. Creates and inserts the new FunToken mapping. -// -// Parameters: -// - ctx: The SDK context for the transaction. -// - erc20: The Ethereum address of the ERC20 token in HexAddr format. -// -// Returns: -// - funtoken: The created FunToken mapping. -// - err: An error if any step fails, nil otherwise. -// -// Possible errors: -// - If the ERC20 token is already registered as a FunToken. -// - If the ERC20 metadata cannot be retrieved. -// - If the bank coin denom is already registered. -// - If the bank metadata validation fails. -// - If the FunToken insertion fails. -func (k *Keeper) CreateFunTokenFromERC20( - ctx sdk.Context, erc20 eth.HexAddr, -) (funtoken evm.FunToken, err error) { - erc20Addr := erc20.ToAddr() - - // 1 | ERC20 already registered with FunToken? - if funtokens := k.FunTokens.Collect( - ctx, k.FunTokens.Indexes.ERC20Addr.ExactMatch(ctx, erc20Addr), - ); len(funtokens) > 0 { - return funtoken, fmt.Errorf("funtoken mapping already created for ERC20 \"%s\"", erc20Addr.Hex()) +/* +Mint implements "ERC20Minter.mint" from ERC20Minter.sol. +See [nibiru/x/evm/embeds]. + + ```solidity + /// @dev Moves `amount` tokens from the caller's account to `to`. + /// Returns a boolean value indicating whether the operation succeeded. + /// Emits a {Transfer} event. + function mint(address to, uint256 amount) public virtual onlyOwner { + _mint(to, amount); } + ``` - // 2 | Get existing ERC20 metadata - info, err := k.FindERC20Metadata(ctx, erc20Addr) +[nibiru/x/evm/embeds]: https://github.com/NibiruChain/nibiru/tree/main/x/evm/embeds +*/ +func (e erc20Calls) Mint( + contract, from, to gethcommon.Address, amount *big.Int, + ctx sdk.Context, +) (evmResp *evm.MsgEthereumTxResponse, err error) { + input, err := e.ABI.Pack("mint", to, amount) if err != nil { return } - bankDenom := fmt.Sprintf("erc20/%s", erc20.String()) - - // 3 | Coin already registered with FunToken? - _, isAlreadyCoin := k.bankKeeper.GetDenomMetaData(ctx, bankDenom) - if isAlreadyCoin { - return funtoken, fmt.Errorf("bank coin denom already registered with denom \"%s\"", bankDenom) - } - if funtokens := k.FunTokens.Collect( - ctx, k.FunTokens.Indexes.BankDenom.ExactMatch(ctx, bankDenom), - ); len(funtokens) > 0 { - return funtoken, fmt.Errorf("funtoken mapping already created for bank denom \"%s\"", bankDenom) - } + commit := true + return e.CallContractWithInput(ctx, from, &contract, commit, input) +} - // 4 | Set bank coin denom metadata in state - bankMetadata := bank.Metadata{ - Description: fmt.Sprintf("Bank coin representation of ERC20 token \"%s\"", erc20.String()), - DenomUnits: []*bank.DenomUnit{ - { - Denom: bankDenom, - Exponent: 0, - }, - }, - Base: bankDenom, - Display: bankDenom, - Name: bankDenom, - Symbol: info.Symbol, - } +/* +Transfer implements "ERC20.transfer" - err = bankMetadata.Validate() + ```solidity + /// @dev Moves `amount` tokens from the caller's account to `to`. + /// Returns a boolean value indicating whether the operation succeeded. + /// Emits a {Transfer} event. + function transfer(address to, uint256 amount) external returns (bool); + ``` +*/ +func (e erc20Calls) Transfer( + contract, from, to gethcommon.Address, amount *big.Int, + ctx sdk.Context, +) (evmResp *evm.MsgEthereumTxResponse, err error) { + input, err := e.ABI.Pack("transfer", to, amount) if err != nil { return } - k.bankKeeper.SetDenomMetaData(ctx, bankMetadata) + commit := true + return e.CallContractWithInput(ctx, from, &contract, commit, input) +} - // 5 | Officially create the funtoken mapping - funtoken = evm.FunToken{ - Erc20Addr: erc20, - BankDenom: bankDenom, - IsMadeFromCoin: false, - } +// BalanceOf retrieves the balance of an ERC20 token for a specific account. +// Implements "ERC20.balanceOf". +func (e erc20Calls) BalanceOf( + contract, account gethcommon.Address, + ctx sdk.Context, +) (out *big.Int, err error) { + return e.LoadERC20BigInt(ctx, e.ABI, contract, "balanceOf", account) +} - return funtoken, k.FunTokens.SafeInsert( - ctx, funtoken.Erc20Addr.ToAddr(), - funtoken.BankDenom, - funtoken.IsMadeFromCoin, - ) +/* +Burn implements "ERC20Burnable.burn" + + ```solidity + /// @dev Destroys `amount` tokens from the caller. + function burn(uint256 amount) public virtual { + ``` +*/ +func (e erc20Calls) Burn( + contract, from gethcommon.Address, amount *big.Int, + ctx sdk.Context, +) (evmResp *evm.MsgEthereumTxResponse, err error) { + input, err := e.ABI.Pack("burn", amount) + if err != nil { + return + } + commit := true + return e.CallContractWithInput(ctx, from, &contract, commit, input) } // CallContract invokes a smart contract on the method specified by [methodName] @@ -189,7 +137,7 @@ func (k Keeper) CallContract( ) (evmResp *evm.MsgEthereumTxResponse, err error) { contractInput, err := abi.Pack(methodName, args...) if err != nil { - err = errors.Wrap(err, "failed to pack ABI args") + err = fmt.Errorf("failed to pack ABI args: %w", err) return } return k.CallContractWithInput(ctx, fromAcc, contract, commit, contractInput) @@ -249,7 +197,7 @@ func (k Keeper) CallContractWithInput( // Apply EVM message cfg, err := k.GetEVMConfig( ctx, - ctx.BlockHeader().ProposerAddress, + sdk.ConsAddress(ctx.BlockHeader().ProposerAddress), k.EthChainID(ctx), ) if err != nil { @@ -272,6 +220,16 @@ func (k Keeper) CallContractWithInput( return evmResp, err } +// computeCommitGasLimit: If the transition is meant to mutate state, this +// function computes an appopriates gas limit for the call with "contractInput" +// bytes against the given contract address. +// +// ℹ️ This creates a cached context for gas estimation, which is essential +// because state transitions can occur outside of the EVM that are triggered +// by Ethereum transactions, like in the case of precompiled contract or +// custom EVM hook that runs after tx execution. Gas estimation in that case +// could mutate the "ctx" object and result in falty resulting state, so we +// must cache here to avoid this issue. func computeCommitGasLimit( commit bool, gasLimit uint64, @@ -284,8 +242,7 @@ func computeCommitGasLimit( return gasLimit, nil } - // Create a cached context for gas estimation - cachedCtx, _ := ctx.CacheContext() + cachedCtx, _ := ctx.CacheContext() // IMPORTANT! jsonArgs, err := json.Marshal(evm.JsonTxArgs{ From: fromAcc, @@ -414,78 +371,3 @@ func (k Keeper) LoadERC20BigInt( } return erc20Val.Value, err } - -func (k Keeper) ERC20() Erc20Calls { - return Erc20Calls{ - Keeper: &k, - ABI: embeds.Contract_ERC20Minter.ABI, - } -} - -type Erc20Calls struct { - *Keeper - ABI gethabi.ABI -} - -func (e Erc20Calls) Mint( - contract, from, to gethcommon.Address, amount *big.Int, - ctx sdk.Context, -) (evmResp *evm.MsgEthereumTxResponse, err error) { - input, err := e.ABI.Pack("mint", to, amount) - if err != nil { - return - } - commit := true - return e.CallContractWithInput(ctx, from, &contract, commit, input) -} - -/* -Transfer implements "ERC20.transfer" - -```solidity -/// @dev Moves `amount` tokens from the caller's account to `to`. -/// Returns a boolean value indicating whether the operation succeeded. -/// Emits a {Transfer} event. -function transfer(address to, uint256 amount) external returns (bool); -``` -*/ -func (e Erc20Calls) Transfer( - contract, from, to gethcommon.Address, amount *big.Int, - ctx sdk.Context, -) (evmResp *evm.MsgEthereumTxResponse, err error) { - input, err := e.ABI.Pack("transfer", to, amount) - if err != nil { - return - } - commit := true - return e.CallContractWithInput(ctx, from, &contract, commit, input) -} - -// BalanceOf retrieves the balance of an ERC20 token for a specific account. -// Implements "ERC20.balanceOf". -func (e Erc20Calls) BalanceOf( - contract, account gethcommon.Address, - ctx sdk.Context, -) (out *big.Int, err error) { - return e.LoadERC20BigInt(ctx, e.ABI, contract, "balanceOf", account) -} - -/* -Burn implements "ERC20Burnable.burn" - -```solidity -/// @dev Destroys `amount` tokens from the caller. -function burn(uint256 amount) public virtual { -``` -*/ -func (e Erc20Calls) Burn( - contract, from gethcommon.Address, amount *big.Int, - ctx sdk.Context, -) (evmResp *evm.MsgEthereumTxResponse, err error) { - input, err := e.ABI.Pack("burn", amount) - if err != nil { - return - } - commit := true - return e.CallContractWithInput(ctx, from, &contract, commit, input) -} diff --git a/x/evm/keeper/erc20_test.go b/x/evm/keeper/erc20_test.go index 7aa8cda28..6878ab64e 100644 --- a/x/evm/keeper/erc20_test.go +++ b/x/evm/keeper/erc20_test.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/common/testutil/testapp" "github.com/NibiruChain/nibiru/x/evm" "github.com/NibiruChain/nibiru/x/evm/embeds" "github.com/NibiruChain/nibiru/x/evm/evmtest" @@ -74,6 +75,15 @@ func (s *Suite) TestCreateFunTokenFromERC20() { _, err = deps.K.Code(deps.Ctx, queryCodeReq) s.NoError(err) + // Give the sender funds for the fee + err = testapp.FundAccount( + deps.Chain.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.K.FeeForCreateFunToken(deps.Ctx), + ) + s.Require().NoError(err) + createFuntokenResp, err := deps.K.CreateFunToken( deps.GoCtx(), &evm.MsgCreateFunToken{ @@ -91,6 +101,15 @@ func (s *Suite) TestCreateFunTokenFromERC20() { }) s.T().Log("sad: CreateFunToken for the ERC20: already registered") + // Give the sender funds for the fee + err = testapp.FundAccount( + deps.Chain.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.K.FeeForCreateFunToken(deps.Ctx), + ) + s.Require().NoError(err) + _, err = deps.K.CreateFunToken( deps.GoCtx(), &evm.MsgCreateFunToken{ @@ -166,6 +185,15 @@ func (s *Suite) TestCreateFunTokenFromCoin() { setBankDenomMetadata(deps.Ctx, deps.Chain.BankKeeper, bankDenom) s.T().Log("happy: CreateFunToken for the bank coin") + // Give the sender funds for the fee + err = testapp.FundAccount( + deps.Chain.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.K.FeeForCreateFunToken(deps.Ctx), + ) + s.Require().NoError(err) + createFuntokenResp, err := deps.K.CreateFunToken( deps.GoCtx(), &evm.MsgCreateFunToken{ @@ -173,7 +201,7 @@ func (s *Suite) TestCreateFunTokenFromCoin() { Sender: deps.Sender.NibiruAddr.String(), }, ) - s.NoError(err, "bankDenom %s", bankDenom) + s.Require().NoError(err, "bankDenom %s", bankDenom) erc20 := createFuntokenResp.FuntokenMapping.Erc20Addr erc20Addr := erc20.ToAddr() s.Equal( @@ -202,6 +230,14 @@ func (s *Suite) TestCreateFunTokenFromCoin() { s.Equal(metadata, info) s.T().Log("sad: CreateFunToken for the bank coin: already registered") + // Give the sender funds for the fee + err = testapp.FundAccount( + deps.Chain.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.K.FeeForCreateFunToken(deps.Ctx), + ) + s.Require().NoError(err) _, err = deps.K.CreateFunToken( deps.GoCtx(), &evm.MsgCreateFunToken{ @@ -209,7 +245,7 @@ func (s *Suite) TestCreateFunTokenFromCoin() { Sender: deps.Sender.NibiruAddr.String(), }, ) - s.ErrorContains(err, "funtoken mapping already created") + s.Require().ErrorContains(err, "funtoken mapping already created") } // TestSendFunTokenToEvm executes sending fun tokens from bank coin to erc20 and checks the results: @@ -265,6 +301,15 @@ func (s *Suite) TestSendFunTokenToEvm() { ) s.Require().NoError(err) + // Give the sender funds for the fee + err = testapp.FundAccount( + deps.Chain.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.K.FeeForCreateFunToken(deps.Ctx), + ) + s.Require().NoError(err) + // Create fun token from coin createFunTokenResp, err := deps.K.CreateFunToken( ctx, @@ -359,7 +404,7 @@ func (s *Suite) TestERC20Calls() { from := theUser to := theUser _, err := deps.K.ERC20().Mint(contract, from, to, big.NewInt(69_420), deps.Ctx) - s.ErrorContains(err, "Ownable: caller is not the owner") + s.ErrorContains(err, evm.ErrOwnable) } s.T().Log("Mint tokens - Success") @@ -370,8 +415,8 @@ func (s *Suite) TestERC20Calls() { _, err := deps.K.ERC20().Mint(contract, from, to, big.NewInt(69_420), deps.Ctx) s.NoError(err) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theUser, big.NewInt(0)) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theEvm, big.NewInt(69_420)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theUser, big.NewInt(0)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theEvm, big.NewInt(69_420)) } s.T().Log("Transfer - Not enough funds") @@ -381,8 +426,8 @@ func (s *Suite) TestERC20Calls() { _, err := deps.K.ERC20().Transfer(contract, from, to, big.NewInt(9_420), deps.Ctx) s.ErrorContains(err, "ERC20: transfer amount exceeds balance") // balances unchanged - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theUser, big.NewInt(0)) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theEvm, big.NewInt(69_420)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theUser, big.NewInt(0)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theEvm, big.NewInt(69_420)) } s.T().Log("Transfer - Success (sanity check)") @@ -391,8 +436,8 @@ func (s *Suite) TestERC20Calls() { to := theUser _, err := deps.K.ERC20().Transfer(contract, from, to, big.NewInt(9_420), deps.Ctx) s.NoError(err) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theUser, big.NewInt(9_420)) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theEvm, big.NewInt(60_000)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theUser, big.NewInt(9_420)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theEvm, big.NewInt(60_000)) } s.T().Log("Burn tokens - Allowed as non-owner") @@ -405,7 +450,7 @@ func (s *Suite) TestERC20Calls() { _, err = deps.K.ERC20().Burn(contract, from, big.NewInt(6_000), deps.Ctx) s.NoError(err) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theUser, big.NewInt(9_000)) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theEvm, big.NewInt(54_000)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theUser, big.NewInt(9_000)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theEvm, big.NewInt(54_000)) } } diff --git a/x/evm/keeper/erc20_from_coin.go b/x/evm/keeper/funtoken_from_coin.go similarity index 91% rename from x/evm/keeper/erc20_from_coin.go rename to x/evm/keeper/funtoken_from_coin.go index 151d48552..feee6572e 100644 --- a/x/evm/keeper/erc20_from_coin.go +++ b/x/evm/keeper/funtoken_from_coin.go @@ -60,6 +60,10 @@ func (k *Keeper) CreateFunTokenFromCoin( func (k *Keeper) DeployERC20ForBankCoin( ctx sdk.Context, bankCoin bank.Metadata, ) (erc20Addr gethcommon.Address, err error) { + // bank.Metadata validation guarantees that both "Base" and "Display" denoms + // pass "sdk.ValidateDenom" and that the "DenomUnits" slice has exponents in + // ascending order with at least one element, which must be the base + // denomination and have exponent 0. decimals := uint8(0) if len(bankCoin.DenomUnits) > 0 { decimalsIdx := len(bankCoin.DenomUnits) - 1 diff --git a/x/evm/keeper/funtoken_from_erc20.go b/x/evm/keeper/funtoken_from_erc20.go new file mode 100644 index 000000000..72dd4552a --- /dev/null +++ b/x/evm/keeper/funtoken_from_erc20.go @@ -0,0 +1,161 @@ +// Copyright (c) 2023-2024 Nibi, Inc. +package keeper + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + bank "github.com/cosmos/cosmos-sdk/x/bank/types" + gethabi "github.com/ethereum/go-ethereum/accounts/abi" + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/common" + "github.com/NibiruChain/nibiru/x/evm" + "github.com/NibiruChain/nibiru/x/evm/embeds" +) + +// FindERC20Metadata retrieves the metadata of an ERC20 token. +// +// Parameters: +// - ctx: The SDK context for the transaction. +// - contract: The Ethereum address of the ERC20 contract. +// +// Returns: +// - info: ERC20Metadata containing name, symbol, and decimals. +// - err: An error if metadata retrieval fails. +func (k Keeper) FindERC20Metadata( + ctx sdk.Context, + contract gethcommon.Address, +) (info ERC20Metadata, err error) { + var abi gethabi.ABI = embeds.Contract_ERC20Minter.ABI + + errs := []error{} + + // Load name, symbol, decimals + name, err := k.LoadERC20Name(ctx, abi, contract) + errs = append(errs, err) + symbol, err := k.LoadERC20Symbol(ctx, abi, contract) + errs = append(errs, err) + decimals, err := k.LoadERC20Decimals(ctx, abi, contract) + errs = append(errs, err) + + err = common.CombineErrors(errs...) + if err != nil { + err = fmt.Errorf("failed to \"FindERC20Metadata\": %w", err) + return info, err + } + + return ERC20Metadata{ + Name: name, + Symbol: symbol, + Decimals: decimals, + }, nil +} + +type ERC20Metadata struct { + Name string + Symbol string + Decimals uint8 +} + +type ( + ERC20String struct{ Value string } + // ERC20Uint8: Unpacking type for "uint8" from Solidity. This is only used in + // the "ERC20.decimals" function. + ERC20Uint8 struct{ Value uint8 } + ERC20Bool struct{ Value bool } + // ERC20BigInt: Unpacking type for "uint256" from Solidity. + ERC20BigInt struct{ Value *big.Int } +) + +// CreateFunTokenFromERC20 creates a new FunToken mapping from an existing ERC20 token. +// +// This function performs the following steps: +// 1. Checks if the ERC20 token is already registered as a FunToken. +// 2. Retrieves the metadata of the existing ERC20 token. +// 3. Verifies that the corresponding bank coin denom is not already registered. +// 4. Sets the bank coin denom metadata in the state. +// 5. Creates and inserts the new FunToken mapping. +// +// Parameters: +// - ctx: The SDK context for the transaction. +// - erc20: The Ethereum address of the ERC20 token in HexAddr format. +// +// Returns: +// - funtoken: The created FunToken mapping. +// - err: An error if any step fails, nil otherwise. +// +// Possible errors: +// - If the ERC20 token is already registered as a FunToken. +// - If the ERC20 metadata cannot be retrieved. +// - If the bank coin denom is already registered. +// - If the bank metadata validation fails. +// - If the FunToken insertion fails. +func (k *Keeper) CreateFunTokenFromERC20( + ctx sdk.Context, erc20 eth.HexAddr, +) (funtoken evm.FunToken, err error) { + erc20Addr := erc20.ToAddr() + + // 1 | ERC20 already registered with FunToken? + if funtokens := k.FunTokens.Collect( + ctx, k.FunTokens.Indexes.ERC20Addr.ExactMatch(ctx, erc20Addr), + ); len(funtokens) > 0 { + return funtoken, fmt.Errorf("funtoken mapping already created for ERC20 \"%s\"", erc20Addr.Hex()) + } + + // 2 | Get existing ERC20 metadata + info, err := k.FindERC20Metadata(ctx, erc20Addr) + if err != nil { + return + } + bankDenom := fmt.Sprintf("erc20/%s", erc20.String()) + + // 3 | Coin already registered with FunToken? + _, isAlreadyCoin := k.bankKeeper.GetDenomMetaData(ctx, bankDenom) + if isAlreadyCoin { + return funtoken, fmt.Errorf("bank coin denom already registered with denom \"%s\"", bankDenom) + } + if funtokens := k.FunTokens.Collect( + ctx, k.FunTokens.Indexes.BankDenom.ExactMatch(ctx, bankDenom), + ); len(funtokens) > 0 { + return funtoken, fmt.Errorf("funtoken mapping already created for bank denom \"%s\"", bankDenom) + } + + // 4 | Set bank coin denom metadata in state + bankMetadata := bank.Metadata{ + Description: fmt.Sprintf( + "ERC20 token \"%s\" represented as a bank coin with corresponding FunToken mapping", erc20.String(), + ), + DenomUnits: []*bank.DenomUnit{ + { + Denom: bankDenom, + Exponent: 0, + }, + }, + Base: bankDenom, + Display: bankDenom, + Name: bankDenom, + Symbol: info.Symbol, + } + + err = bankMetadata.Validate() + if err != nil { + return + } + k.bankKeeper.SetDenomMetaData(ctx, bankMetadata) + + // 5 | Officially create the funtoken mapping + funtoken = evm.FunToken{ + Erc20Addr: erc20, + BankDenom: bankDenom, + IsMadeFromCoin: false, + } + + return funtoken, k.FunTokens.SafeInsert( + ctx, funtoken.Erc20Addr.ToAddr(), + funtoken.BankDenom, + funtoken.IsMadeFromCoin, + ) +} diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index b24df1e3e..3265f9b8f 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -507,9 +507,12 @@ func (k *Keeper) CreateFunToken( return } - // TODO: UD-DEBUG: feat: Add fee upon registration. - + // Deduct fee upon registration. ctx := sdk.UnwrapSDKContext(goCtx) + err = k.deductCreateFunTokenFee(ctx, msg) + if err != nil { + return + } emptyErc20 := msg.FromErc20 == nil || msg.FromErc20.Size() == 0 switch { @@ -538,6 +541,25 @@ func (k *Keeper) CreateFunToken( }, err } +func (k Keeper) deductCreateFunTokenFee(ctx sdk.Context, msg *evm.MsgCreateFunToken) error { + fee := k.FeeForCreateFunToken(ctx) + from := sdk.MustAccAddressFromBech32(msg.Sender) // validation in msg.ValidateBasic + + if err := k.bankKeeper.SendCoinsFromAccountToModule( + ctx, from, evm.ModuleName, fee); err != nil { + return fmt.Errorf("unable to pay the \"create_fun_token_fee\": %w", err) + } + if err := k.bankKeeper.BurnCoins(ctx, evm.ModuleName, fee); err != nil { + return fmt.Errorf("failed to burn the \"create_fun_token_fee\" after payment: %w", err) + } + return nil +} + +func (k Keeper) FeeForCreateFunToken(ctx sdk.Context) sdk.Coins { + evmParams := k.GetParams(ctx) + return sdk.NewCoins(sdk.NewCoin(evmParams.EvmDenom, evmParams.CreateFuntokenFee)) +} + // SendFunTokenToEvm Sends a coin with a valid "FunToken" mapping to the // given recipient address ("to_eth_addr") in the corresponding ERC20 // representation. diff --git a/x/evm/keeper/precompiles.go b/x/evm/keeper/precompiles.go index d1fdd4801..a972c7915 100644 --- a/x/evm/keeper/precompiles.go +++ b/x/evm/keeper/precompiles.go @@ -3,11 +3,8 @@ package keeper import ( "bytes" - "fmt" - "maps" "sort" - sdk "github.com/cosmos/cosmos-sdk/types" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" @@ -48,40 +45,6 @@ func (k *Keeper) AddPrecompiles(precompileMap map[gethcommon.Address]vm.Precompi // Check if there is sufficient demand for this. } -// AddEVMExtensions adds the given precompiles to the list of active precompiles in the EVM parameters -// and to the available precompiles map in the Keeper. This function returns an error if -// the precompiles are invalid or duplicated. -func (k *Keeper) AddEVMExtensions( - ctx sdk.Context, precompiles ...vm.PrecompiledContract, -) error { - params := k.GetParams(ctx) - - // precompileAddrs := make([]string, len(precompiles)) - precompileAddrs := set.New[string]() - precompilesMap := maps.Clone(k.precompiles) - - for _, precompile := range precompiles { - // add to active precompiles - address := precompile.Address() - precompileAddrs.Add(address.String()) - - // add to available precompiles, but check for duplicates - if _, ok := precompilesMap[address]; ok { - return fmt.Errorf("precompile already registered: %s", address) - } - precompilesMap[address] = precompile - } - - params.ActivePrecompiles = append(params.ActivePrecompiles, precompileAddrs.ToSlice()...) - - // NOTE: the active precompiles are sorted and validated before setting them - // in the params - k.SetParams(ctx, params) - // update the pointer to the map with the newly added EVM Extensions - k.precompiles = precompilesMap - return nil -} - // IsAvailablePrecompile returns true if the given precompile address is contained in the // EVM keeper's available precompiles map. func (k Keeper) IsAvailablePrecompile(address gethcommon.Address) bool { @@ -93,6 +56,10 @@ func (k Keeper) IsAvailablePrecompile(address gethcommon.Address) bool { // // NOTE: uses index based approach instead of append because it's supposed to be faster. // Check https://stackoverflow.com/questions/21362950/getting-a-slice-of-keys-from-a-map. +// +// TODO: refactor(evm/keeper/precompiles): Use ordered map as the underlying +// struct to remove the need for iterating over k.precompiles in so many +// different ways. The set could also be tracked as well to make it ea func (k Keeper) PrecompileAddrsSorted() []gethcommon.Address { addresses := make([]gethcommon.Address, len(k.precompiles)) i := 0 diff --git a/x/evm/params.go b/x/evm/params.go index 80989d6ff..736dbbbfe 100644 --- a/x/evm/params.go +++ b/x/evm/params.go @@ -7,6 +7,7 @@ import ( "strings" errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v7/modules/core/24-host" @@ -15,26 +16,17 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "golang.org/x/exp/slices" + "github.com/NibiruChain/nibiru/app/appconst" "github.com/NibiruChain/nibiru/eth" ) const ( // DefaultEVMDenom defines the default EVM denomination - DefaultEVMDenom = "unibi" + DefaultEVMDenom = appconst.BondDenom ) -var ( - // DefaultAllowUnprotectedTxs rejects all unprotected txs (i.e false) - DefaultAllowUnprotectedTxs = false - // DefaultEnableCreate enables contract creation (i.e true) - DefaultEnableCreate = true - // DefaultEnableCall enables contract calls (i.e true) - DefaultEnableCall = true - // AvailableEVMExtensions defines the default active precompiles - AvailableEVMExtensions = []string{} - DefaultExtraEIPs = []int64{} - DefaultEVMChannels = []string{} -) +// AvailableEVMExtensions defines the default active precompiles +var AvailableEVMExtensions = []string{} // DefaultParams returns default evm parameters // ExtraEIPs is empty to prevent overriding the latest hard fork instruction set @@ -43,12 +35,13 @@ var ( func DefaultParams() Params { return Params{ EvmDenom: DefaultEVMDenom, - EnableCreate: DefaultEnableCreate, - EnableCall: DefaultEnableCall, - ExtraEIPs: DefaultExtraEIPs, - AllowUnprotectedTxs: DefaultAllowUnprotectedTxs, + EnableCreate: true, + EnableCall: true, + ExtraEIPs: []int64{}, + AllowUnprotectedTxs: false, ActivePrecompiles: AvailableEVMExtensions, - EVMChannels: DefaultEVMChannels, + EVMChannels: []string{}, + CreateFuntokenFee: math.NewIntWithDecimal(10_000, 6), // 10_000 NIBI } } diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 361b38881..80e06e157 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -59,7 +59,7 @@ func (p precompileFunToken) Run( }() contractInput := contract.Input - ctx, method, args, err := OnStart(p, evm, contractInput) + ctx, method, args, err := OnRunStart(p, evm, contractInput) if err != nil { return nil, err } diff --git a/x/evm/precompile/funtoken_test.go b/x/evm/precompile/funtoken_test.go index c7765189e..dfbf2a782 100644 --- a/x/evm/precompile/funtoken_test.go +++ b/x/evm/precompile/funtoken_test.go @@ -86,8 +86,8 @@ func (s *Suite) FunToken_HappyPath() { contract := funtoken.Erc20Addr.ToAddr() s.T().Log("Balances of the ERC20 should start empty") - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theUser, big.NewInt(0)) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theEvm, big.NewInt(0)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theUser, big.NewInt(0)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theEvm, big.NewInt(0)) s.T().Log("Mint tokens - Fail from non-owner") { @@ -108,8 +108,8 @@ func (s *Suite) FunToken_HappyPath() { _, err = evmtest.DoEthTx(&deps, contract, from, input) s.NoError(err) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theUser, big.NewInt(69_420)) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theEvm, big.NewInt(0)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theUser, big.NewInt(69_420)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theEvm, big.NewInt(0)) } s.T().Log("Transfer - Success (sanity check)") @@ -119,8 +119,8 @@ func (s *Suite) FunToken_HappyPath() { to := theEvm _, err := deps.K.ERC20().Transfer(contract, from, to, big.NewInt(1), deps.Ctx) s.NoError(err) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theUser, big.NewInt(69_419)) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theEvm, big.NewInt(1)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theUser, big.NewInt(69_419)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theEvm, big.NewInt(1)) s.Equal("0", deps.Chain.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount.String(), ) @@ -137,14 +137,14 @@ func (s *Suite) FunToken_HappyPath() { _, err = evmtest.DoEthTx(&deps, precompileAddr.ToAddr(), from, input) s.Require().NoError(err) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theUser, big.NewInt(69_419-amtToSend)) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theEvm, big.NewInt(1)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theUser, big.NewInt(69_419-amtToSend)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theEvm, big.NewInt(1)) s.Equal(fmt.Sprintf("%d", amtToSend), deps.Chain.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount.String(), ) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theUser, big.NewInt(69_000)) - evmtest.AssertERC20BalanceEqual(s.T(), &deps, contract, theEvm, big.NewInt(1)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theUser, big.NewInt(69_000)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, theEvm, big.NewInt(1)) s.Equal("419", deps.Chain.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount.String(), ) diff --git a/x/evm/precompile/precompile.go b/x/evm/precompile/precompile.go index 9060021fb..05c2ed192 100644 --- a/x/evm/precompile/precompile.go +++ b/x/evm/precompile/precompile.go @@ -96,7 +96,7 @@ func ABIMethodByID(abi gethabi.ABI, sigdata []byte) (*gethabi.Method, error) { return nil, fmt.Errorf("no method with id: %#x", sigdata[:4]) } -func OnStart( +func OnRunStart( p NibiruPrecompile, evm *vm.EVM, input []byte, ) (ctx sdk.Context, method *gethabi.Method, args []interface{}, err error) { // 1 | Get context from StateDB