Skip to content

Commit 0c1e0b9

Browse files
authored
feat: add getErc20Address method to IFunToken (#2260)
* feat: add getErc20Address method to IFunToken and implement its logic in precompile * feat: changelog * fix: remove debug print * feat: enhance getErc20Address method to validate token factory denominations * fix: lint and fixes
1 parent c6bf93d commit 0c1e0b9

File tree

12 files changed

+241
-31
lines changed

12 files changed

+241
-31
lines changed

CHANGELOG.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5151
- [#2251](https://github.com/NibiruChain/nibiru/pull/2251) - feat(evm): add ERC20 contract with metadata updates
5252
- [#2249](https://github.com/NibiruChain/nibiru/pull/2249) - fix(evm): resetting gas meter for afterOp in bank extension
5353
- [#2257](https://github.com/NibiruChain/nibiru/pull/2257) - fix: simulation tests by register interfaces for vesting and use correct app keys field
54+
- [#2260](https://github.com/NibiruChain/nibiru/pull/2260) - feat(evm): add getErc20Address method to IFunToken
5455
- [#2259](https://github.com/NibiruChain/nibiru/pull/2259) - feat: add depinject wiring for all sdk modules
5556
- [#2261](https://github.com/NibiruChain/nibiru/pull/2261) - feat: gen pulsar api and app wiring for sudo
5657
- [#2262](https://github.com/NibiruChain/nibiru/pull/2262) - feat: app wiring for oracle
@@ -82,7 +83,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8283
- [#2206](https://github.com/NibiruChain/nibiru/pull/2206) - ci(chaosnet): fix docker image build
8384
- [#2207](https://github.com/NibiruChain/nibiru/pull/2207) - chore(ci): add cache for chaosnet builds
8485
- [#2209](https://github.com/NibiruChain/nibiru/pull/2209) - refator(ci):
85-
Simplify GitHub actions based on conditional paths, removing the need for files like ".github/workflows/skip-unit-tests.yml".
86+
Simplify GitHub actions based on conditional paths, removing the need for files like ".github/workflows/skip-unit-tests.yml".
8687
- [#2211](https://github.com/NibiruChain/nibiru/pull/2211) - ci(chaosnet): avoid building on cache injected directories
8788
- [#2212](https://github.com/NibiruChain/nibiru/pull/2212) - fix(evm): proper eth tx logs emission for funtoken operations
8889
- [#2213](https://github.com/NibiruChain/nibiru/pull/2213) - chore(build): include lib versions on cache
@@ -123,14 +124,14 @@ Simplify GitHub actions based on conditional paths, removing the need for files
123124
- [#2145](https://github.com/NibiruChain/nibiru/pull/2145) - chore(token-registry): add xNIBI Astrovault LST to registry
124125
- [#2147](https://github.com/NibiruChain/nibiru/pull/2147) - fix(simapp): manually add x/vesting Cosmos-SDK module types to the codec in simulation tests since they are expected by default
125126
- [#2149](https://github.com/NibiruChain/nibiru/pull/2149) - feat(evm-oracle):
126-
add Solidity contract that we can use to expose the Nibiru Oracle in the
127-
ChainLink interface. Publish all precompiled contracts and ABIs on npm under
128-
the `@nibiruchain/solidity` package.
127+
add Solidity contract that we can use to expose the Nibiru Oracle in the
128+
ChainLink interface. Publish all precompiled contracts and ABIs on npm under
129+
the `@nibiruchain/solidity` package.
129130
- [#2151](https://github.com/NibiruChain/nibiru/pull/2151) - feat(evm): randao support for evm
130131
- [#2152](https://github.com/NibiruChain/nibiru/pull/2152) - fix(precompile): consume gas for precompile calls regardless of error
131132
- [#2154](https://github.com/NibiruChain/nibiru/pull/2154) - fix(evm):
132-
JSON encoding for the `EIP55Addr` struct was not following the Go conventions and
133-
needed to include double quotes around the hexadecimal string.
133+
JSON encoding for the `EIP55Addr` struct was not following the Go conventions and
134+
needed to include double quotes around the hexadecimal string.
134135
- [#2156](https://github.com/NibiruChain/nibiru/pull/2156) - test(evm-e2e): add E2E test using the Nibiru Oracle's ChainLink impl
135136
- [#2157](https://github.com/NibiruChain/nibiru/pull/2157) - fix(evm): Fix unit inconsistency related to AuthInfo.Fee and txData.Fee using effective fee
136137
- [#2159](https://github.com/NibiruChain/nibiru/pull/2159) - chore(evm): Augment the Wasm msg handler so that wasm contracts cannot send MsgEthereumTx

x/evm/embeds/artifacts/contracts/IFunToken.sol/IFunToken.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,25 @@
155155
"stateMutability": "nonpayable",
156156
"type": "function"
157157
},
158+
{
159+
"inputs": [
160+
{
161+
"internalType": "string",
162+
"name": "bankDenom",
163+
"type": "string"
164+
}
165+
],
166+
"name": "getErc20Address",
167+
"outputs": [
168+
{
169+
"internalType": "address",
170+
"name": "erc20Address",
171+
"type": "address"
172+
}
173+
],
174+
"stateMutability": "view",
175+
"type": "function"
176+
},
158177
{
159178
"inputs": [
160179
{

x/evm/embeds/artifacts/contracts/TestERC20TransferThenPrecompileSend.sol/TestERC20TransferThenPrecompileSend.json

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

x/evm/embeds/artifacts/contracts/TestFunTokenPrecompileLocalGas.sol/TestFunTokenPrecompileLocalGas.json

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

x/evm/embeds/artifacts/contracts/TestInfiniteRecursionERC20.sol/TestInfiniteRecursionERC20.json

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

x/evm/embeds/artifacts/contracts/TestNativeSendThenPrecompileSend.sol/TestNativeSendThenPrecompileSend.json

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

x/evm/embeds/artifacts/contracts/TestPrecompileSelfCallRevert.sol/TestPrecompileSelfCallRevert.json

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

x/evm/embeds/artifacts/contracts/TestPrecompileSendToBankThenERC20Transfer.sol/TestPrecompileSendToBankThenERC20Transfer.json

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

x/evm/embeds/contracts/IFunToken.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ interface IFunToken is INibiruEvm {
2323
string calldata to
2424
) external returns (uint256 sentAmount);
2525

26+
/// @notice Retrieves the ERC20 contract address associated with a given bank denomination.
27+
/// @param bankDenom The bank denomination string (e.g., "unibi", "erc20/0x...", "ibc/...").
28+
/// @return erc20Address The corresponding ERC20 contract address, or address(0) if no mapping exists.
29+
function getErc20Address(
30+
string memory bankDenom
31+
) external view returns (address erc20Address);
32+
2633
struct NibiruAccount {
2734
address ethAddr;
2835
string bech32Addr;

x/evm/precompile/funtoken.go

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
gethcommon "github.com/ethereum/go-ethereum/common"
1313
"github.com/ethereum/go-ethereum/core/vm"
1414

15+
tftypes "github.com/NibiruChain/nibiru/v2/x/tokenfactory/types"
16+
1517
"github.com/NibiruChain/nibiru/v2/app/keepers"
1618
"github.com/NibiruChain/nibiru/v2/eth"
1719
"github.com/NibiruChain/nibiru/v2/x/evm"
@@ -40,12 +42,13 @@ func (p precompileFunToken) ABI() *gethabi.ABI {
4042
}
4143

4244
const (
43-
FunTokenMethod_sendToBank PrecompileMethod = "sendToBank"
44-
FunTokenMethod_balance PrecompileMethod = "balance"
45-
FunTokenMethod_bankBalance PrecompileMethod = "bankBalance"
46-
FunTokenMethod_whoAmI PrecompileMethod = "whoAmI"
47-
FunTokenMethod_sendToEvm PrecompileMethod = "sendToEvm"
48-
FunTokenMethod_bankMsgSend PrecompileMethod = "bankMsgSend"
45+
FunTokenMethod_sendToBank PrecompileMethod = "sendToBank"
46+
FunTokenMethod_balance PrecompileMethod = "balance"
47+
FunTokenMethod_bankBalance PrecompileMethod = "bankBalance"
48+
FunTokenMethod_whoAmI PrecompileMethod = "whoAmI"
49+
FunTokenMethod_sendToEvm PrecompileMethod = "sendToEvm"
50+
FunTokenMethod_bankMsgSend PrecompileMethod = "bankMsgSend"
51+
FunTokenMethod_getErc20Address PrecompileMethod = "getErc20Address"
4952
)
5053

5154
// Run runs the precompiled contract
@@ -79,6 +82,8 @@ func (p precompileFunToken) Run(
7982
bz, err = p.sendToEvm(startResult, contract.CallerAddress, readonly, evm)
8083
case FunTokenMethod_bankMsgSend:
8184
bz, err = p.bankMsgSend(startResult, contract.CallerAddress, readonly)
85+
case FunTokenMethod_getErc20Address:
86+
bz, err = p.getErc20Address(startResult, contract)
8287
default:
8388
// Note that this code path should be impossible to reach since
8489
// "[decomposeInput]" parses methods directly from the ABI.
@@ -477,14 +482,12 @@ func (p precompileFunToken) whoAmI(
477482
if err := assertContractQuery(contract); err != nil {
478483
return bz, err
479484
}
480-
481485
addrEth, addrBech32, err := p.parseArgsWhoAmI(args)
482486
if err != nil {
483487
err = ErrInvalidArgs(err)
484488
return
485489
}
486-
487-
return method.Outputs.Pack([]any{
490+
bz, err = method.Outputs.Pack([]any{
488491
struct {
489492
EthAddr gethcommon.Address `json:"ethAddr"`
490493
Bech32Addr string `json:"bech32Addr"`
@@ -493,6 +496,7 @@ func (p precompileFunToken) whoAmI(
493496
Bech32Addr: addrBech32.String(),
494497
},
495498
}...)
499+
return bz, err
496500
}
497501

498502
func (p precompileFunToken) parseArgsWhoAmI(args []any) (
@@ -724,6 +728,92 @@ func (p precompileFunToken) bankMsgSend(
724728
return method.Outputs.Pack(true)
725729
}
726730

731+
// getErc20Address implements "IFunToken.getErc20Address"
732+
// It looks up the FunToken mapping by the bank denomination and returns the associated ERC20 address.
733+
//
734+
// ```solidity
735+
// function getErc20Address(string memory bankDenom) external view returns (address erc20Address);
736+
// ```
737+
func (p precompileFunToken) getErc20Address(
738+
start OnRunStartResult,
739+
contract *vm.Contract, // Needed for assertContractQuery
740+
) (bz []byte, err error) {
741+
method, args, ctx := start.Method, start.Args, start.CacheCtx
742+
defer func() {
743+
if err != nil {
744+
err = ErrMethodCalled(method, err)
745+
}
746+
}()
747+
// Ensure this is called in a read-only context (like view or pure)
748+
if err := assertContractQuery(contract); err != nil {
749+
return bz, err
750+
}
751+
752+
bankDenom, err := p.parseArgsGetErc20Address(args)
753+
if err != nil {
754+
err = ErrInvalidArgs(err)
755+
return
756+
}
757+
758+
// Perform lookup using the BankDenom index
759+
iterator := p.evmKeeper.FunTokens.Indexes.BankDenom.ExactMatch(ctx, bankDenom)
760+
mappings := p.evmKeeper.FunTokens.Collect(ctx, iterator)
761+
762+
erc20ResultAddress := gethcommon.Address{} // Default to address(0)
763+
764+
if len(mappings) == 1 {
765+
erc20ResultAddress = mappings[0].Erc20Addr.Address
766+
} else if len(mappings) > 1 {
767+
err = fmt.Errorf(
768+
"multiple FunToken mappings found for bank denom \"%s\": %d",
769+
bankDenom, len(mappings),
770+
)
771+
return
772+
} else {
773+
// No mapping found, erc20ResultAddress remains address(0)
774+
err = fmt.Errorf(
775+
"no FunToken mapping found for bank denom \"%s\"", bankDenom,
776+
)
777+
return
778+
}
779+
780+
// Pack the result (either the found address or address(0))
781+
return method.Outputs.Pack(erc20ResultAddress)
782+
}
783+
784+
// parseArgsGetErc20Address parses the arguments for the getErc20Address method.
785+
// Expected arguments: (string memory bankDenom)
786+
func (p precompileFunToken) parseArgsGetErc20Address(args []any) (
787+
bankDenom string,
788+
err error,
789+
) {
790+
if e := assertNumArgs(args, 1); e != nil {
791+
err = e
792+
return
793+
}
794+
795+
argIdx := 0
796+
bankDenom, ok := args[argIdx].(string)
797+
if !ok {
798+
err = ErrArgTypeValidation("string bankDenom", args[argIdx])
799+
return
800+
}
801+
802+
// Validate the bank denomination format using Cosmos SDK validation
803+
if err = sdk.ValidateDenom(bankDenom); err != nil {
804+
// maybe it's a tf denom
805+
tfDenom := tftypes.DenomStr(bankDenom)
806+
807+
if err = tfDenom.Validate(); err != nil {
808+
809+
err = fmt.Errorf("invalid bank denomination format: %w", err)
810+
return
811+
}
812+
}
813+
814+
return bankDenom, nil
815+
}
816+
727817
func parseArgsBankMsgSend(args []any) (toStr, denom string, amount *big.Int, err error) {
728818
if e := assertNumArgs(args, 3); e != nil {
729819
err = e

0 commit comments

Comments
 (0)