From 62d4b1ab14d9c2ebceaaf2739fb633a79a91c286 Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:13:29 -0500 Subject: [PATCH] fix(evm-keeper): better utilize ERC20 metadata during FunToken creation (#2074) * fix(evm-keeper): better utilize ERC20 metadata during FunToken creation * chore: changelog --- CHANGELOG.md | 1 + proto/buf.yaml | 5 +- x/evm/keeper/funtoken_from_erc20.go | 84 +++++++++++++++++++----- x/evm/keeper/funtoken_from_erc20_test.go | 17 +++++ 4 files changed, 89 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3617c4c..7466f1bac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,6 +128,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2060](https://github.com/NibiruChain/nibiru/pull/2060) - fix(evm-precompiles): add assertNumArgs validation - [#2056](https://github.com/NibiruChain/nibiru/pull/2056) - feat(evm): add oracle precompile - [#2065](https://github.com/NibiruChain/nibiru/pull/2065) - refactor(evm)!: Refactor out dead code from the evm.Params +- [#2073](https://github.com/NibiruChain/nibiru/pull/2073) - fix(evm-keeper): better utilize ERC20 metadata during FunToken creation #### Dapp modules: perp, spot, oracle, etc diff --git a/proto/buf.yaml b/proto/buf.yaml index 1ab1ff5ff..8c7df280c 100644 --- a/proto/buf.yaml +++ b/proto/buf.yaml @@ -1,5 +1,6 @@ version: v1 -name: buf.build/NibiruChain/nibiru +# Corresponds to Buf Schema Registry repo: https://buf.build/nibiru-chain/nibiru +name: buf.build/nibiru-chain/nibiru deps: - buf.build/cosmos/cosmos-proto - buf.build/cosmos/cosmos-sdk:v0.47.3 @@ -22,4 +23,4 @@ lint: - PACKAGE_DIRECTORY_MATCH ignore: - - tendermint \ No newline at end of file + - tendermint diff --git a/x/evm/keeper/funtoken_from_erc20.go b/x/evm/keeper/funtoken_from_erc20.go index ab7186aa3..e10f0cca8 100644 --- a/x/evm/keeper/funtoken_from_erc20.go +++ b/x/evm/keeper/funtoken_from_erc20.go @@ -50,6 +50,22 @@ func (k Keeper) FindERC20Metadata( }, nil } +// ERC20Metadata: Optional metadata fields parsed from an ERC20 contract. +// The [Wrapped Ether contract] is a good example for reference. +// +// ```solidity +// constract WETH9 { +// string public name = "Wrapped Ether"; +// string public symbol = "WETH" +// uint8 public decimals = 18; +// } +// ``` +// +// Note that the name and symbol fields may be empty, according to the [ERC20 +// specification]. +// +// [Wrapped Ether contract]: https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code +// [ERC20 specification]: https://eips.ethereum.org/EIPS/eip-20 type ERC20Metadata struct { Name string Symbol string @@ -98,7 +114,7 @@ func (k *Keeper) createFunTokenFromERC20( } // 2 | Get existing ERC20 metadata - info, err := k.FindERC20Metadata(ctx, erc20) + erc20Info, err := k.FindERC20Metadata(ctx, erc20) if err != nil { return funtoken, err } @@ -115,21 +131,7 @@ func (k *Keeper) createFunTokenFromERC20( } // 4 | Set bank coin denom metadata in state - bankMetadata := bank.Metadata{ - Description: fmt.Sprintf( - "ERC20 token \"%s\" represented as a bank coin with a corresponding FunToken mapping", erc20.String(), - ), - DenomUnits: []*bank.DenomUnit{ - { - Denom: bankDenom, - Exponent: 0, // TODO(k-yang): determine which exponent to use - }, - }, - Base: bankDenom, - Display: bankDenom, - Name: bankDenom, - Symbol: info.Symbol, - } + bankMetadata := erc20Info.ToBankMetadata(bankDenom, erc20) err = bankMetadata.Validate() if err != nil { @@ -150,3 +152,53 @@ func (k *Keeper) createFunTokenFromERC20( ctx, erc20, bankDenom, false, ) } + +// ToBankMetadata produces the "bank.Metadata" corresponding to a FunToken +// mapping created from an ERC20 token. +// +// The first argument of DenomUnits is required and the official base unit +// onchain, meaning the denom must be equivalent to bank.Metadata.Base. +// +// Decimals for an ERC20 are synonymous to "bank.DenomUnit.Exponent" in what +// they mean for external clients like wallets. +func (erc20Info ERC20Metadata) ToBankMetadata( + bankDenom string, erc20 gethcommon.Address, +) bank.Metadata { + var symbol string + if erc20Info.Symbol != "" { + symbol = erc20Info.Symbol + } else { + symbol = bankDenom + } + + var name string + if erc20Info.Name != "" { + name = erc20Info.Name + } else { + name = bankDenom + } + + denomUnits := []*bank.DenomUnit{ + { + Denom: bankDenom, + Exponent: 0, + }, + } + display := symbol + if erc20Info.Decimals > 0 { + denomUnits = append(denomUnits, &bank.DenomUnit{ + Denom: display, + Exponent: uint32(erc20Info.Decimals), + }) + } + return bank.Metadata{ + Description: fmt.Sprintf( + "ERC20 token \"%s\" represented as a Bank Coin with a corresponding FunToken mapping", erc20.String(), + ), + DenomUnits: denomUnits, + Base: bankDenom, + Display: display, + Name: name, + Symbol: symbol, + } +} diff --git a/x/evm/keeper/funtoken_from_erc20_test.go b/x/evm/keeper/funtoken_from_erc20_test.go index e09d4e748..3eaf1462c 100644 --- a/x/evm/keeper/funtoken_from_erc20_test.go +++ b/x/evm/keeper/funtoken_from_erc20_test.go @@ -7,6 +7,7 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + bank "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/suite" @@ -101,6 +102,22 @@ func (s *FunTokenFromErc20Suite) TestCreateFunTokenFromERC20() { IsMadeFromCoin: false, }, ) + bankMetadata, _ := deps.App.BankKeeper.GetDenomMetaData(deps.Ctx, expectedBankDenom) + s.Require().Equal(bank.Metadata{ + Description: fmt.Sprintf( + "ERC20 token \"%s\" represented as a Bank Coin with a corresponding FunToken mapping", erc20Addr.String(), + ), + DenomUnits: []*bank.DenomUnit{ + {Denom: expectedBankDenom, Exponent: 0}, + {Denom: metadata.Symbol, Exponent: uint32(metadata.Decimals)}, + }, + Base: expectedBankDenom, + Display: metadata.Symbol, + Name: metadata.Name, + Symbol: metadata.Symbol, + URI: "", + URIHash: "", + }, bankMetadata) s.T().Log("sad: CreateFunToken for the ERC20: already registered") // Give the sender funds for the fee