diff --git a/CHANGELOG.md b/CHANGELOG.md index 5139ee102..ce42aaaf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1996](https://github.com/NibiruChain/nibiru/pull/1996) - perf(evm-keeper-precompile): implement sorted map for `k.precompiles` to remove dead code - [#1997](https://github.com/NibiruChain/nibiru/pull/1997) - refactor(evm): Remove unnecessary params: "enable_call", "enable_create". - [#2000](https://github.com/NibiruChain/nibiru/pull/2000) - refactor(evm): simplify ERC-20 keeper methods +- [#2001](https://github.com/NibiruChain/nibiru/pull/2001) - refactor(evm): simplify FunToken methods and tests #### Dapp modules: perp, spot, oracle, etc diff --git a/eth/rpc/backend/call_tx.go b/eth/rpc/backend/call_tx.go index 98c2342c9..7f61bf880 100644 --- a/eth/rpc/backend/call_tx.go +++ b/eth/rpc/backend/call_tx.go @@ -50,7 +50,7 @@ func (b *Backend) Resend(args evm.JsonTxArgs, gasPrice *hexutil.Big, gasLimit *h signer := gethcore.LatestSigner(cfg) - matchTx := args.ToTransaction().AsTransaction() + matchTx := args.ToMsgEthTx().AsTransaction() // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. price := matchTx.GasPrice() diff --git a/eth/rpc/backend/sign_tx.go b/eth/rpc/backend/sign_tx.go index 3d6379d97..7d3032ea3 100644 --- a/eth/rpc/backend/sign_tx.go +++ b/eth/rpc/backend/sign_tx.go @@ -49,7 +49,7 @@ func (b *Backend) SendTransaction(args evm.JsonTxArgs) (common.Hash, error) { // the corresponding chainID validation, we need to sign the transaction before calling it // Sign transaction - msg := args.ToTransaction() + msg := args.ToMsgEthTx() if err := msg.Sign(signer, b.clientCtx.Keyring); err != nil { b.logger.Debug("failed to sign tx", "error", err.Error()) return common.Hash{}, err diff --git a/eth/rpc/backend/sign_tx_test.go b/eth/rpc/backend/sign_tx_test.go index 93c13cfe2..559289d3f 100644 --- a/eth/rpc/backend/sign_tx_test.go +++ b/eth/rpc/backend/sign_tx_test.go @@ -128,7 +128,7 @@ func (s *BackendSuite) TestSendTransaction() { queryClient := s.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterParamsWithoutHeader(queryClient, 1) ethSigner := gethcore.LatestSigner(s.backend.ChainConfig()) - msg := callArgsDefault.ToTransaction() + msg := callArgsDefault.ToMsgEthTx() err := msg.Sign(ethSigner, s.backend.clientCtx.Keyring) s.Require().NoError(err) tc.expHash = msg.AsTransaction().Hash() @@ -263,7 +263,7 @@ func broadcastTx( RegisterBaseFee(queryClient, baseFee) RegisterParamsWithoutHeader(queryClient, 1) ethSigner := gethcore.LatestSigner(s.backend.ChainConfig()) - msg := callArgsDefault.ToTransaction() + msg := callArgsDefault.ToMsgEthTx() err = msg.Sign(ethSigner, s.backend.clientCtx.Keyring) s.Require().NoError(err) tx, _ := msg.BuildTx(s.backend.clientCtx.TxConfig.NewTxBuilder(), evm.DefaultEVMDenom) diff --git a/eth/rpc/rpcapi/eth_api.go b/eth/rpc/rpcapi/eth_api.go index 9b59be6bc..75f7ffc85 100644 --- a/eth/rpc/rpcapi/eth_api.go +++ b/eth/rpc/rpcapi/eth_api.go @@ -471,7 +471,7 @@ func (e *EthAPI) FillTransaction( } // Assemble the transaction and obtain rlp - tx := args.ToTransaction().AsTransaction() + tx := args.ToMsgEthTx().AsTransaction() data, err := tx.MarshalBinary() if err != nil { diff --git a/x/evm/evmmodule/genesis_test.go b/x/evm/evmmodule/genesis_test.go index 15c8dcd66..10e61efbd 100644 --- a/x/evm/evmmodule/genesis_test.go +++ b/x/evm/evmmodule/genesis_test.go @@ -45,7 +45,7 @@ func (s *Suite) TestExportInitGenesis() { amountToSendC := big.NewInt(228) // Create ERC-20 contract - deployResp, err := evmtest.DeployContract(&deps, erc20Contract, s.T()) + deployResp, err := evmtest.DeployContract(&deps, erc20Contract) s.Require().NoError(err) erc20Addr := deployResp.ContractAddr totalSupply, err := deps.EvmKeeper.ERC20().LoadERC20BigInt( diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index 03f8e4226..ea6055454 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -141,38 +141,41 @@ type DeployContractResult struct { func DeployContract( deps *TestDeps, contract embeds.CompiledEvmContract, - t *testing.T, args ...any, -) (result DeployContractResult, err error) { +) (result *DeployContractResult, err error) { // Use contract args packedArgs, err := contract.ABI.Pack("", args...) if err != nil { - err = errors.Wrap(err, "failed to pack ABI args") - return + return nil, errors.Wrap(err, "failed to pack contract args") } bytecodeForCall := append(contract.Bytecode, packedArgs...) nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) - jsonTxArgs := evm.JsonTxArgs{ - Nonce: (*hexutil.Uint64)(&nonce), - Input: (*hexutil.Bytes)(&bytecodeForCall), - From: &deps.Sender.EthAddr, + msgEthTx, err := GenerateAndSignEthTxMsg( + evm.JsonTxArgs{ + Nonce: (*hexutil.Uint64)(&nonce), + Input: (*hexutil.Bytes)(&bytecodeForCall), + From: &deps.Sender.EthAddr, + }, deps, + ) + if err != nil { + return nil, errors.Wrap(err, "failed to generate and sign eth tx msg") } - ethTxMsg, err := GenerateAndSignEthTxMsg(jsonTxArgs, deps) - require.NoError(t, err) - resp, err := deps.App.EvmKeeper.EthereumTx(sdk.WrapSDKContext(deps.Ctx), ethTxMsg) - require.NoError(t, err) - require.Empty(t, resp.VmError) - - contractAddress := crypto.CreateAddress(deps.Sender.EthAddr, nonce) + resp, err := deps.App.EvmKeeper.EthereumTx(sdk.WrapSDKContext(deps.Ctx), msgEthTx) + if err != nil { + return nil, errors.Wrap(err, "failed to execute ethereum tx") + } + if resp.VmError != "" { + return nil, fmt.Errorf("vm error: %s", resp.VmError) + } - return DeployContractResult{ + return &DeployContractResult{ TxResp: resp, - EthTxMsg: ethTxMsg, + EthTxMsg: msgEthTx, ContractData: contract, Nonce: nonce, - ContractAddr: contractAddress, + ContractAddr: crypto.CreateAddress(deps.Sender.EthAddr, nonce), }, nil } @@ -181,7 +184,7 @@ func DeployAndExecuteERC20Transfer( deps *TestDeps, t *testing.T, ) (*evm.MsgEthereumTx, []*evm.MsgEthereumTx) { // TX 1: Deploy ERC-20 contract - deployResp, err := DeployContract(deps, embeds.SmartContract_TestERC20, t) + deployResp, err := DeployContract(deps, embeds.SmartContract_TestERC20) require.NoError(t, err) contractData := deployResp.ContractData nonce := deployResp.Nonce @@ -217,9 +220,9 @@ func DeployAndExecuteERC20Transfer( // GenerateAndSignEthTxMsg estimates gas, sets gas limit and sings the tx func GenerateAndSignEthTxMsg( - txArgs evm.JsonTxArgs, deps *TestDeps, + jsonTxArgs evm.JsonTxArgs, deps *TestDeps, ) (*evm.MsgEthereumTx, error) { - estimateArgs, err := json.Marshal(&txArgs) + estimateArgs, err := json.Marshal(&jsonTxArgs) if err != nil { return nil, err } @@ -235,11 +238,11 @@ func GenerateAndSignEthTxMsg( if err != nil { return nil, err } - txArgs.Gas = (*hexutil.Uint64)(&res.Gas) + jsonTxArgs.Gas = (*hexutil.Uint64)(&res.Gas) - msgEthereumTx := txArgs.ToTransaction() + msgEthTx := jsonTxArgs.ToMsgEthTx() gethSigner := gethcore.LatestSignerForChainID(deps.App.EvmKeeper.EthChainID(deps.Ctx)) - return msgEthereumTx, msgEthereumTx.Sign(gethSigner, deps.Sender.KeyringSigner) + return msgEthTx, msgEthTx.Sign(gethSigner, deps.Sender.KeyringSigner) } func TransferWei( diff --git a/x/evm/json_tx_args.go b/x/evm/json_tx_args.go index 38e3a1a4b..603546632 100644 --- a/x/evm/json_tx_args.go +++ b/x/evm/json_tx_args.go @@ -56,9 +56,9 @@ func (args *JsonTxArgs) String() string { args.AccessList) } -// ToTransaction converts the arguments to an ethereum transaction. +// ToMsgEthTx converts the arguments to an ethereum transaction. // This assumes that setTxDefaults has been called. -func (args *JsonTxArgs) ToTransaction() *MsgEthereumTx { +func (args *JsonTxArgs) ToMsgEthTx() *MsgEthereumTx { var ( chainID, value, gasPrice, maxFeePerGas, maxPriorityFeePerGas sdkmath.Int gas, nonce uint64 diff --git a/x/evm/json_tx_args_test.go b/x/evm/json_tx_args_test.go index 49ade7a6a..fb38cc6ca 100644 --- a/x/evm/json_tx_args_test.go +++ b/x/evm/json_tx_args_test.go @@ -94,7 +94,7 @@ func (suite *TxDataTestSuite) TestConvertTxArgsEthTx() { }, } for _, tc := range testCases { - res := tc.txArgs.ToTransaction() + res := tc.txArgs.ToMsgEthTx() suite.Require().NotNil(res) } } diff --git a/x/evm/keeper/erc20.go b/x/evm/keeper/erc20.go index fc7d52128..3874c7729 100644 --- a/x/evm/keeper/erc20.go +++ b/x/evm/keeper/erc20.go @@ -6,6 +6,7 @@ import ( "fmt" "math/big" + "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" gethabi "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" @@ -137,8 +138,7 @@ func (k Keeper) CallContract( ) (evmResp *evm.MsgEthereumTxResponse, err error) { contractInput, err := abi.Pack(methodName, args...) if err != nil { - err = fmt.Errorf("failed to pack ABI args: %w", err) - return + return nil, fmt.Errorf("failed to pack ABI args: %w", err) } return k.CallContractWithInput(ctx, fromAcc, contract, commit, contractInput) } @@ -176,7 +176,7 @@ func (k Keeper) CallContractWithInput( commit, gasLimit, &fromAcc, contract, contractInput, k, ctx, ) if err != nil { - return evmResp, err + return nil, err } unusedBigInt := big.NewInt(0) @@ -201,7 +201,7 @@ func (k Keeper) CallContractWithInput( k.EthChainID(ctx), ) if err != nil { - return evmResp, fmt.Errorf("failed to load evm config: %s", err) + return nil, errors.Wrapf(err, "failed to load evm config") } txConfig := statedb.NewEmptyTxConfig(gethcommon.BytesToHash(ctx.HeaderHash())) @@ -209,11 +209,11 @@ func (k Keeper) CallContractWithInput( ctx, evmMsg, evm.NewNoOpTracer(), commit, evmCfg, txConfig, ) if err != nil { - return evmResp, err + return nil, errors.Wrapf(err, "failed to apply EVM message") } if evmResp.Failed() { - return evmResp, fmt.Errorf("%w: EVM error: %s", err, evmResp.VmError) + return nil, errors.Wrapf(err, "EVM execution failed: %s", evmResp.VmError) } return evmResp, err @@ -249,8 +249,7 @@ func computeCommitGasLimit( Data: (*hexutil.Bytes)(&contractInput), }) if err != nil { - err = fmt.Errorf("failed compute gas limit to marshal tx args: %w", err) - return + return gasLimit, fmt.Errorf("failed compute gas limit to marshal tx args: %w", err) } gasRes, err := k.EstimateGasForEvmCallType( @@ -262,12 +261,10 @@ func computeCommitGasLimit( evm.CallTypeSmart, ) if err != nil { - err = fmt.Errorf("failed to compute gas limit: %w", err) - return + return gasLimit, fmt.Errorf("failed to compute gas limit: %w", err) } - newGasLimit = gasRes.Gas - return newGasLimit, nil + return gasRes.Gas, nil } func (k Keeper) LoadERC20Name( diff --git a/x/evm/keeper/erc20_test.go b/x/evm/keeper/erc20_test.go index 20946553f..a35bb6891 100644 --- a/x/evm/keeper/erc20_test.go +++ b/x/evm/keeper/erc20_test.go @@ -2,419 +2,16 @@ package keeper_test import ( - "fmt" "math/big" - "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" bank "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/NibiruChain/nibiru/v2/x/common/testutil" - - "github.com/NibiruChain/nibiru/v2/eth" - "github.com/NibiruChain/nibiru/v2/x/common/testutil/testapp" "github.com/NibiruChain/nibiru/v2/x/evm" - "github.com/NibiruChain/nibiru/v2/x/evm/embeds" "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" - "github.com/NibiruChain/nibiru/v2/x/evm/keeper" ) -func (s *Suite) TestCreateFunTokenFromERC20() { - deps := evmtest.NewTestDeps() - - // Compute contract address. FindERC20 should fail - expectedERC20Addr := crypto.CreateAddress(deps.Sender.EthAddr, deps.StateDB().GetNonce(deps.Sender.EthAddr)) - _, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, expectedERC20Addr) - s.Error(err) - - s.T().Log("Case 1: Deploy and invoke ERC20 with 18 decimals") - { - metadata := keeper.ERC20Metadata{ - Name: "erc20name", - Symbol: "TOKEN", - Decimals: 18, - } - deployResp, err := evmtest.DeployContract( - &deps, embeds.SmartContract_ERC20Minter, s.T(), - metadata.Name, metadata.Symbol, metadata.Decimals, - ) - s.Require().NoError(err) - s.Equal(expectedERC20Addr, deployResp.ContractAddr) - - info, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, deployResp.ContractAddr) - s.NoError(err, info) - s.Equal(metadata, info) - } - - s.T().Log("Case 2: Deploy and invoke ERC20 with 9 decimals") - { - metadata := keeper.ERC20Metadata{ - Name: "gwei", - Symbol: "GWEI", - Decimals: 9, - } - expectedERC20Addr = crypto.CreateAddress(deps.Sender.EthAddr, deps.StateDB().GetNonce(deps.Sender.EthAddr)) - deployResp, err := evmtest.DeployContract( - &deps, embeds.SmartContract_ERC20Minter, s.T(), - metadata.Name, metadata.Symbol, metadata.Decimals, - ) - s.Require().NoError(err) - s.Require().Equal(expectedERC20Addr, deployResp.ContractAddr) - - info, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, deployResp.ContractAddr) - s.NoError(err, info) - s.Equal(metadata, info) - - queryCodeReq := &evm.QueryCodeRequest{ - Address: expectedERC20Addr.String(), - } - _, err = deps.EvmKeeper.Code(deps.Ctx, queryCodeReq) - s.Require().NoError(err) - } - - erc20Addr := eth.NewHexAddr(expectedERC20Addr) - s.T().Log("happy: CreateFunToken for the ERC20") - { - // Give the sender funds for the fee - s.Require().NoError(testapp.FundAccount( - deps.App.BankKeeper, - deps.Ctx, - deps.Sender.NibiruAddr, - deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx), - )) - - resp, err := deps.EvmKeeper.CreateFunToken( - sdk.WrapSDKContext(deps.Ctx), - &evm.MsgCreateFunToken{ - FromErc20: &erc20Addr, - Sender: deps.Sender.NibiruAddr.String(), - }, - ) - s.Require().NoError(err, "erc20 %s", erc20Addr) - - expectedBankDenom := fmt.Sprintf("erc20/%s", erc20Addr.String()) - s.Equal( - resp.FuntokenMapping, - evm.FunToken{ - Erc20Addr: erc20Addr, - BankDenom: expectedBankDenom, - IsMadeFromCoin: false, - }) - - // Event "EventFunTokenCreated" must present - testutil.RequireContainsTypedEvent( - s.T(), - deps.Ctx, - &evm.EventFunTokenCreated{ - BankDenom: expectedBankDenom, - Erc20ContractAddress: erc20Addr.String(), - Creator: deps.Sender.NibiruAddr.String(), - IsMadeFromCoin: false, - }, - ) - } - - s.T().Log("sad: CreateFunToken for the ERC20: already registered") - { - // Give the sender funds for the fee - s.Require().NoError(testapp.FundAccount( - deps.App.BankKeeper, - deps.Ctx, - deps.Sender.NibiruAddr, - deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx), - )) - - _, err = deps.EvmKeeper.CreateFunToken( - sdk.WrapSDKContext(deps.Ctx), - &evm.MsgCreateFunToken{ - FromErc20: &erc20Addr, - Sender: deps.Sender.NibiruAddr.String(), - }, - ) - s.ErrorContains(err, "funtoken mapping already created") - } - - s.T().Log("sad: CreateFunToken for the ERC20: invalid sender") - { - _, err = deps.EvmKeeper.CreateFunToken( - sdk.WrapSDKContext(deps.Ctx), - &evm.MsgCreateFunToken{ - FromErc20: &erc20Addr, - }, - ) - s.ErrorContains(err, "invalid sender") - } -} - -func (s *Suite) TestDeployERC20ForBankCoin() { - deps := evmtest.NewTestDeps() - - // Compute contract address. FindERC20 should fail - nonce := deps.StateDB().GetNonce(evm.EVM_MODULE_ADDRESS) - expectedERC20Addr := crypto.CreateAddress(evm.EVM_MODULE_ADDRESS, nonce) - _, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, expectedERC20Addr) - s.Error(err) - - s.T().Log("Case 1: Deploy and invoke ERC20 for info") - bankDenom := "sometoken" - bankMetadata := bank.Metadata{ - DenomUnits: []*bank.DenomUnit{ - { - Denom: bankDenom, - Exponent: 0, - }, - }, - Base: bankDenom, - Display: bankDenom, - Name: bankDenom, - Symbol: "TOKEN", - } - erc20Addr, err := deps.EvmKeeper.DeployERC20ForBankCoin( - deps.Ctx, bankMetadata, - ) - s.Require().NoError(err) - s.Equal(expectedERC20Addr, erc20Addr) - - s.T().Log("Expect ERC20 metadata on contract") - info, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, erc20Addr) - s.NoError(err) - s.Equal(keeper.ERC20Metadata{ - Name: bankDenom, - Symbol: "TOKEN", - Decimals: 0, - }, info) -} - -func (s *Suite) TestCreateFunTokenFromCoin() { - deps := evmtest.NewTestDeps() - - // Compute contract address. FindERC20 should fail - nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) - contractAddress := crypto.CreateAddress(deps.Sender.EthAddr, nonce) - _, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, contractAddress) - s.Error(err) - - s.T().Log("Setup: Create a coin in the bank state") - bankDenom := "sometoken" - - setBankDenomMetadata(deps.Ctx, deps.App.BankKeeper, bankDenom) - - s.T().Log("happy: CreateFunToken for the bank coin") - // Give the sender funds for the fee - s.Require().NoError(testapp.FundAccount( - deps.App.BankKeeper, - deps.Ctx, - deps.Sender.NibiruAddr, - deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx), - )) - - createFuntokenResp, err := deps.EvmKeeper.CreateFunToken( - sdk.WrapSDKContext(deps.Ctx), - &evm.MsgCreateFunToken{ - FromBankDenom: bankDenom, - Sender: deps.Sender.NibiruAddr.String(), - }, - ) - s.Require().NoError(err, "bankDenom %s", bankDenom) - erc20 := createFuntokenResp.FuntokenMapping.Erc20Addr - erc20Addr := erc20.ToAddr() - s.Equal( - createFuntokenResp.FuntokenMapping, - evm.FunToken{ - Erc20Addr: erc20, - BankDenom: bankDenom, - IsMadeFromCoin: true, - }) - - s.T().Log("Expect ERC20 to be deployed") - queryCodeReq := &evm.QueryCodeRequest{ - Address: erc20.String(), - } - _, err = deps.EvmKeeper.Code(deps.Ctx, queryCodeReq) - s.Require().NoError(err) - - s.T().Log("Expect ERC20 metadata on contract") - metadata := keeper.ERC20Metadata{ - Name: bankDenom, - Symbol: "TOKEN", - Decimals: 0, - } - info, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, erc20Addr) - s.NoError(err, info) - s.Equal(metadata, info) - - // Event "EventFunTokenCreated" must present - // Event "EventFunTokenCreated" must present - testutil.RequireContainsTypedEvent( - s.T(), - deps.Ctx, - &evm.EventFunTokenCreated{ - BankDenom: bankDenom, - Erc20ContractAddress: erc20.String(), - Creator: deps.Sender.NibiruAddr.String(), - IsMadeFromCoin: true, - }, - ) - - s.T().Log("sad: CreateFunToken for the bank coin: already registered") - // Give the sender funds for the fee - err = testapp.FundAccount( - deps.App.BankKeeper, - deps.Ctx, - deps.Sender.NibiruAddr, - deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx), - ) - s.Require().NoError(err) - _, err = deps.EvmKeeper.CreateFunToken( - sdk.WrapSDKContext(deps.Ctx), - &evm.MsgCreateFunToken{ - FromBankDenom: bankDenom, - Sender: deps.Sender.NibiruAddr.String(), - }, - ) - s.Require().ErrorContains(err, "funtoken mapping already created") -} - -// TestSendFunTokenToEvm executes sending fun tokens from bank coin to erc20 and checks the results: -// - sender balance should be reduced by sendAmount -// - erc-20 balance should be increased by sendAmount -// - evm module account should hold sender's coins -func (s *Suite) TestSendFunTokenToEvm() { - for _, tc := range []struct { - name string - bankDenom string - senderBalanceBefore math.Int - amountToSend math.Int - wantErr string - }{ - { - name: "happy: proper sending", - bankDenom: "unibi", - senderBalanceBefore: math.NewInt(100), - amountToSend: math.NewInt(10), - wantErr: "", - }, - { - name: "sad: not registered bank denom", - bankDenom: "not-registered-denom", - senderBalanceBefore: math.NewInt(100), - amountToSend: math.NewInt(10), - wantErr: "does not exist", - }, - { - name: "sad: insufficient balance", - bankDenom: "unibi", - senderBalanceBefore: math.NewInt(10), - amountToSend: math.NewInt(100), - wantErr: "insufficient funds", - }, - } { - s.Run(tc.name, func() { - deps := evmtest.NewTestDeps() - bankDenom := "unibi" - recipientEVMAddr := eth.MustNewHexAddrFromStr("0x1234500000000000000000000000000000000000") - evmModuleAddr := deps.App.AccountKeeper.GetModuleAddress(evm.ModuleName) - spendableAmount := tc.senderBalanceBefore.Int64() - spendableCoins := sdk.NewCoins(sdk.NewInt64Coin(bankDenom, spendableAmount)) - - ctx := sdk.WrapSDKContext(deps.Ctx) - setBankDenomMetadata(deps.Ctx, deps.App.BankKeeper, bankDenom) - - // Fund sender's wallet - err := deps.App.BankKeeper.MintCoins(deps.Ctx, evm.ModuleName, spendableCoins) - s.Require().NoError(err) - err = deps.App.BankKeeper.SendCoinsFromModuleToAccount( - deps.Ctx, evm.ModuleName, deps.Sender.NibiruAddr, spendableCoins, - ) - s.Require().NoError(err) - - // Give the sender funds for the fee - err = testapp.FundAccount( - deps.App.BankKeeper, - deps.Ctx, - deps.Sender.NibiruAddr, - deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx), - ) - s.Require().NoError(err) - - // Create fun token from coin - createFunTokenResp, err := deps.EvmKeeper.CreateFunToken( - ctx, - &evm.MsgCreateFunToken{ - FromBankDenom: bankDenom, - Sender: deps.Sender.NibiruAddr.String(), - }, - ) - s.Require().NoError(err) - funTokenErc20Addr := createFunTokenResp.FuntokenMapping.Erc20Addr.ToAddr() - - // Send fun token to ERC-20 contract - bankCoin := sdk.Coin{Denom: tc.bankDenom, Amount: tc.amountToSend} - _, err = deps.EvmKeeper.SendFunTokenToEvm( - ctx, - &evm.MsgSendFunTokenToEvm{ - Sender: deps.Sender.NibiruAddr.String(), - BankCoin: bankCoin, - ToEthAddr: recipientEVMAddr, - }, - ) - if tc.wantErr != "" { - s.Require().ErrorContains(err, tc.wantErr) - return - } - s.Require().NoError(err) - - // Event "EventSendFunTokenToEvm" must present - testutil.RequireContainsTypedEvent( - s.T(), - deps.Ctx, - &evm.EventSendFunTokenToEvm{ - Sender: deps.Sender.NibiruAddr.String(), - Erc20ContractAddress: funTokenErc20Addr.String(), - ToEthAddr: recipientEVMAddr.String(), - BankCoin: bankCoin, - }, - ) - - // Check 1: coins are stored on a module balance - moduleBalance, err := deps.App.BankKeeper.Balance(ctx, &bank.QueryBalanceRequest{ - Address: evmModuleAddr.String(), - Denom: bankDenom, - }) - s.Require().NoError(err) - s.Equal(tc.amountToSend, moduleBalance.Balance.Amount) - - // Check 2: Sender balance reduced by send amount - senderBalance, err := deps.App.BankKeeper.Balance(ctx, &bank.QueryBalanceRequest{ - Address: deps.Sender.NibiruAddr.String(), - Denom: bankDenom, - }) - s.Require().NoError(err) - s.Equal(tc.senderBalanceBefore.Sub(tc.amountToSend), senderBalance.Balance.Amount) - - // Check 3: erc-20 balance equals to send amount - recipientERC20Balance, err := deps.EvmKeeper.CallContract( - deps.Ctx, - embeds.SmartContract_ERC20Minter.ABI, - evm.EVM_MODULE_ADDRESS, - &funTokenErc20Addr, - false, - "balanceOf", - recipientEVMAddr.ToAddr(), - ) - s.Require().NoError(err) - res, err := embeds.SmartContract_ERC20Minter.ABI.Unpack( - "balanceOf", recipientERC20Balance.Ret, - ) - s.Require().NoError(err) - s.Equal(1, len(res)) - s.Equal(tc.amountToSend.BigInt(), res[0]) - }) - } -} - // setBankDenomMetadata utility method to set bank denom metadata required for working with coin func setBankDenomMetadata(ctx sdk.Context, bankKeeper bankkeeper.Keeper, bankDenom string) { bankMetadata := bank.Metadata{ @@ -438,61 +35,47 @@ func (s *Suite) TestERC20Calls() { funtoken := evmtest.CreateFunTokenForBankCoin(&deps, bankDenom, &s.Suite) contract := funtoken.Erc20Addr.ToAddr() - theUser := deps.Sender.EthAddr - theEvm := evm.EVM_MODULE_ADDRESS - s.T().Log("Mint tokens - Fail from non-owner") { - from := theUser - to := theUser - _, err := deps.EvmKeeper.ERC20().Mint(contract, from, to, big.NewInt(69_420), deps.Ctx) + _, err := deps.EvmKeeper.ERC20().Mint(contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS, big.NewInt(69_420), deps.Ctx) s.ErrorContains(err, evm.ErrOwnable) } s.T().Log("Mint tokens - Success") { - from := theEvm - to := theEvm - - _, err := deps.EvmKeeper.ERC20().Mint(contract, from, to, big.NewInt(69_420), deps.Ctx) + _, err := deps.EvmKeeper.ERC20().Mint(contract, evm.EVM_MODULE_ADDRESS, evm.EVM_MODULE_ADDRESS, big.NewInt(69_420), deps.Ctx) s.Require().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, deps.Sender.EthAddr, big.NewInt(0)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(69_420)) } s.T().Log("Transfer - Not enough funds") { - from := theUser - to := theEvm - _, err := deps.EvmKeeper.ERC20().Transfer(contract, from, to, big.NewInt(9_420), deps.Ctx) + _, err := deps.EvmKeeper.ERC20().Transfer(contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS, 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, deps.Sender.EthAddr, big.NewInt(0)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(69_420)) } s.T().Log("Transfer - Success (sanity check)") { - from := theEvm - to := theUser - _, err := deps.EvmKeeper.ERC20().Transfer(contract, from, to, big.NewInt(9_420), deps.Ctx) + _, err := deps.EvmKeeper.ERC20().Transfer(contract, evm.EVM_MODULE_ADDRESS, deps.Sender.EthAddr, big.NewInt(9_420), deps.Ctx) s.Require().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, deps.Sender.EthAddr, big.NewInt(9_420)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(60_000)) } s.T().Log("Burn tokens - Allowed as non-owner") { - from := theUser - _, err := deps.EvmKeeper.ERC20().Burn(contract, from, big.NewInt(420), deps.Ctx) + _, err := deps.EvmKeeper.ERC20().Burn(contract, deps.Sender.EthAddr, big.NewInt(420), deps.Ctx) s.Require().NoError(err) - from = theEvm - _, err = deps.EvmKeeper.ERC20().Burn(contract, from, big.NewInt(6_000), deps.Ctx) + _, err = deps.EvmKeeper.ERC20().Burn(contract, evm.EVM_MODULE_ADDRESS, big.NewInt(6_000), deps.Ctx) s.Require().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, deps.Sender.EthAddr, big.NewInt(9_000)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(54_000)) } } diff --git a/x/evm/keeper/funtoken_from_coin.go b/x/evm/keeper/funtoken_from_coin.go index c58d43db9..469657da6 100644 --- a/x/evm/keeper/funtoken_from_coin.go +++ b/x/evm/keeper/funtoken_from_coin.go @@ -23,8 +23,8 @@ func (k *Keeper) CreateFunTokenFromCoin( } // 2 | Check for denom metadata in bank state - bankCoin, isAlreadyCoin := k.bankKeeper.GetDenomMetaData(ctx, bankDenom) - if !isAlreadyCoin { + bankCoin, isFound := k.bankKeeper.GetDenomMetaData(ctx, bankDenom) + if !isFound { return funtoken, fmt.Errorf("bank coin denom should have bank metadata for denom \"%s\"", bankDenom) } @@ -66,24 +66,22 @@ func (k *Keeper) DeployERC20ForBankCoin( decimals = uint8(bankCoin.DenomUnits[decimalsIdx].Exponent) } - erc20Embed := embeds.SmartContract_ERC20Minter - // pass empty method name to deploy the contract - packedArgs, err := erc20Embed.ABI.Pack("", bankCoin.Name, bankCoin.Symbol, decimals) + packedArgs, err := embeds.SmartContract_ERC20Minter.ABI.Pack("", bankCoin.Name, bankCoin.Symbol, decimals) if err != nil { - err = errors.Wrap(err, "failed to pack ABI args") - return gethcommon.Address{}, err + return gethcommon.Address{}, errors.Wrap(err, "failed to pack ABI args") } erc20Addr = crypto.CreateAddress(evm.EVM_MODULE_ADDRESS, k.GetAccNonce(ctx, evm.EVM_MODULE_ADDRESS)) - bytecodeForCall := append(erc20Embed.Bytecode, packedArgs...) + bytecodeForCall := append(embeds.SmartContract_ERC20Minter.Bytecode, packedArgs...) + + // nil address for contract creation _, err = k.CallContractWithInput( ctx, evm.EVM_MODULE_ADDRESS, nil, true, bytecodeForCall, ) if err != nil { - err = errors.Wrap(err, "deploy ERC20 failed") - return + return gethcommon.Address{}, errors.Wrap(err, "failed to deploy ERC20 contract") } return erc20Addr, nil diff --git a/x/evm/keeper/funtoken_from_coin_test.go b/x/evm/keeper/funtoken_from_coin_test.go new file mode 100644 index 000000000..b377dd336 --- /dev/null +++ b/x/evm/keeper/funtoken_from_coin_test.go @@ -0,0 +1,262 @@ +// Copyright (c) 2023-2024 Nibi, Inc. +package keeper_test + +import ( + "testing" + + "cosmossdk.io/math" + 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" + + "github.com/NibiruChain/nibiru/v2/eth" + "github.com/NibiruChain/nibiru/v2/x/common/testutil" + "github.com/NibiruChain/nibiru/v2/x/common/testutil/testapp" + "github.com/NibiruChain/nibiru/v2/x/evm" + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" + "github.com/NibiruChain/nibiru/v2/x/evm/keeper" +) + +func (s *FunTokenFromCoinSuite) TestDeployERC20ForBankCoin() { + deps := evmtest.NewTestDeps() + + // Compute contract address. FindERC20 should fail + nonce := deps.StateDB().GetNonce(evm.EVM_MODULE_ADDRESS) + expectedERC20Addr := crypto.CreateAddress(evm.EVM_MODULE_ADDRESS, nonce) + _, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, expectedERC20Addr) + s.Error(err) + + s.T().Log("Case 1: Deploy and invoke ERC20 for info") + const bankDenom = "sometoken" + bankMetadata := bank.Metadata{ + DenomUnits: []*bank.DenomUnit{ + { + Denom: bankDenom, + Exponent: 0, + }, + }, + Base: bankDenom, + Display: bankDenom, + Name: bankDenom, + Symbol: "TOKEN", + } + erc20Addr, err := deps.EvmKeeper.DeployERC20ForBankCoin( + deps.Ctx, bankMetadata, + ) + s.Require().NoError(err) + s.Equal(expectedERC20Addr, erc20Addr) + + s.T().Log("Expect ERC20 metadata on contract") + info, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, erc20Addr) + s.NoError(err) + s.Equal(keeper.ERC20Metadata{ + Name: bankDenom, + Symbol: "TOKEN", + Decimals: 0, + }, info) +} + +func (s *FunTokenFromCoinSuite) TestCreateFunTokenFromCoin() { + deps := evmtest.NewTestDeps() + + // Compute contract address. FindERC20 should fail + nonce := deps.StateDB().GetNonce(deps.Sender.EthAddr) + contractAddress := crypto.CreateAddress(deps.Sender.EthAddr, nonce) + _, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, contractAddress) + s.Error(err) + + s.T().Log("Setup: Create a coin in the bank state") + bankDenom := "sometoken" + + setBankDenomMetadata(deps.Ctx, deps.App.BankKeeper, bankDenom) + + s.T().Log("happy: CreateFunToken for the bank coin") + // Give the sender funds for the fee + s.Require().NoError(testapp.FundAccount( + deps.App.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx), + )) + + createFuntokenResp, err := deps.EvmKeeper.CreateFunToken( + sdk.WrapSDKContext(deps.Ctx), + &evm.MsgCreateFunToken{ + FromBankDenom: bankDenom, + Sender: deps.Sender.NibiruAddr.String(), + }, + ) + s.Require().NoError(err, "bankDenom %s", bankDenom) + erc20 := createFuntokenResp.FuntokenMapping.Erc20Addr + erc20Addr := erc20.ToAddr() + s.Equal( + createFuntokenResp.FuntokenMapping, + evm.FunToken{ + Erc20Addr: erc20, + BankDenom: bankDenom, + IsMadeFromCoin: true, + }) + + s.T().Log("Expect ERC20 to be deployed") + queryCodeReq := &evm.QueryCodeRequest{ + Address: erc20.String(), + } + _, err = deps.EvmKeeper.Code(deps.Ctx, queryCodeReq) + s.Require().NoError(err) + + s.T().Log("Expect ERC20 metadata on contract") + metadata := keeper.ERC20Metadata{ + Name: bankDenom, + Symbol: "TOKEN", + Decimals: 0, + } + info, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, erc20Addr) + s.NoError(err, info) + s.Equal(metadata, info) + + // Event "EventFunTokenCreated" must present + // Event "EventFunTokenCreated" must present + testutil.RequireContainsTypedEvent( + s.T(), + deps.Ctx, + &evm.EventFunTokenCreated{ + BankDenom: bankDenom, + Erc20ContractAddress: erc20.String(), + Creator: deps.Sender.NibiruAddr.String(), + IsMadeFromCoin: true, + }, + ) + + s.T().Log("sad: CreateFunToken for the bank coin: already registered") + // Give the sender funds for the fee + err = testapp.FundAccount( + deps.App.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx), + ) + s.Require().NoError(err) + _, err = deps.EvmKeeper.CreateFunToken( + sdk.WrapSDKContext(deps.Ctx), + &evm.MsgCreateFunToken{ + FromBankDenom: bankDenom, + Sender: deps.Sender.NibiruAddr.String(), + }, + ) + s.Require().ErrorContains(err, "funtoken mapping already created") +} + +// TestSendFunTokenToEvm executes sending fun tokens from bank coin to erc20 and checks the results: +// - sender balance should be reduced by sendAmount +// - erc-20 balance should be increased by sendAmount +// - evm module account should hold sender's coins +func (s *FunTokenFromCoinSuite) TestSendFunTokenToEvm() { + for _, tc := range []struct { + name string + bankDenom string + initialBalance math.Int + amountToSend math.Int + wantErr string + }{ + { + name: "happy: proper sending", + bankDenom: "unibi", + initialBalance: math.NewInt(100), + amountToSend: math.NewInt(10), + wantErr: "", + }, + { + name: "sad: not registered bank denom", + bankDenom: "not-registered-denom", + initialBalance: math.NewInt(100), + amountToSend: math.NewInt(10), + wantErr: "does not exist", + }, + { + name: "sad: insufficient balance", + bankDenom: "unibi", + initialBalance: math.NewInt(10), + amountToSend: math.NewInt(100), + wantErr: "insufficient funds", + }, + } { + s.Run(tc.name, func() { + deps := evmtest.NewTestDeps() + bankDenom := "unibi" + recipientEVMAddr := eth.MustNewHexAddrFromStr("0x1234500000000000000000000000000000000000") + evmModuleAddr := deps.App.AccountKeeper.GetModuleAddress(evm.ModuleName) + + ctx := sdk.WrapSDKContext(deps.Ctx) + setBankDenomMetadata(deps.Ctx, deps.App.BankKeeper, bankDenom) + + // Give the sender funds + s.Require().NoError(testapp.FundAccount( + deps.App.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx).Add(sdk.NewCoin(bankDenom, tc.initialBalance)), + )) + + // Create fun token from coin + createFunTokenResp, err := deps.EvmKeeper.CreateFunToken( + ctx, + &evm.MsgCreateFunToken{ + FromBankDenom: bankDenom, + Sender: deps.Sender.NibiruAddr.String(), + }, + ) + s.Require().NoError(err) + funTokenErc20Addr := createFunTokenResp.FuntokenMapping.Erc20Addr.ToAddr() + + // Send fun token to ERC-20 contract + bankCoin := sdk.NewCoin(tc.bankDenom, tc.amountToSend) + _, err = deps.EvmKeeper.SendFunTokenToEvm( + ctx, + &evm.MsgSendFunTokenToEvm{ + Sender: deps.Sender.NibiruAddr.String(), + BankCoin: bankCoin, + ToEthAddr: recipientEVMAddr, + }, + ) + if tc.wantErr != "" { + s.Require().ErrorContains(err, tc.wantErr) + return + } + s.Require().NoError(err) + + // Event "EventSendFunTokenToEvm" must present + testutil.RequireContainsTypedEvent( + s.T(), + deps.Ctx, + &evm.EventSendFunTokenToEvm{ + Sender: deps.Sender.NibiruAddr.String(), + Erc20ContractAddress: funTokenErc20Addr.String(), + ToEthAddr: recipientEVMAddr.String(), + BankCoin: bankCoin, + }, + ) + + // Check 1: coins are stored on a module balance + moduleBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, evmModuleAddr, bankDenom) + s.Require().Equal(tc.amountToSend, moduleBalance.Amount) + + // Check 2: Sender balance reduced by send amount + senderBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, deps.Sender.NibiruAddr, bankDenom) + s.Require().Equal(tc.initialBalance.Sub(tc.amountToSend), senderBalance.Amount) + + // Check 3: erc-20 balance equals to send amount + balance, err := deps.EvmKeeper.ERC20().BalanceOf(funTokenErc20Addr, recipientEVMAddr.ToAddr(), deps.Ctx) + s.Require().NoError(err) + s.Require().Zero(balance.Cmp(tc.amountToSend.BigInt())) + }) + } +} + +type FunTokenFromCoinSuite struct { + suite.Suite +} + +func TestFunTokenFromCoinSuite(t *testing.T) { + suite.Run(t, new(FunTokenFromCoinSuite)) +} diff --git a/x/evm/keeper/funtoken_from_erc20.go b/x/evm/keeper/funtoken_from_erc20.go index 9e2dcda5a..c56ecb584 100644 --- a/x/evm/keeper/funtoken_from_erc20.go +++ b/x/evm/keeper/funtoken_from_erc20.go @@ -93,20 +93,19 @@ type ( // - If the bank metadata validation fails. // - If the FunToken insertion fails. func (k *Keeper) CreateFunTokenFromERC20( - ctx sdk.Context, erc20 eth.HexAddr, + ctx sdk.Context, erc20 gethcommon.Address, ) (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()) + if funtokens := k.FunTokens.Collect(ctx, k.FunTokens.Indexes.ERC20Addr.ExactMatch(ctx, erc20)); len(funtokens) > 0 { + return funtoken, fmt.Errorf("funtoken mapping already created for ERC20 \"%s\"", erc20) } // 2 | Get existing ERC20 metadata - info, err := k.FindERC20Metadata(ctx, erc20Addr) + info, err := k.FindERC20Metadata(ctx, erc20) if err != nil { return funtoken, err } + bankDenom := fmt.Sprintf("erc20/%s", erc20.String()) // 3 | Coin already registered with FunToken? @@ -114,7 +113,6 @@ func (k *Keeper) CreateFunTokenFromERC20( if isFound { 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) } @@ -122,12 +120,12 @@ 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 corresponding FunToken mapping", erc20.String(), + "ERC20 token \"%s\" represented as a bank coin with a corresponding FunToken mapping", erc20.String(), ), DenomUnits: []*bank.DenomUnit{ { Denom: bankDenom, - Exponent: 0, + Exponent: 0, // TODO(k-yang): determine which exponent to use }, }, Base: bankDenom, @@ -144,7 +142,7 @@ func (k *Keeper) CreateFunTokenFromERC20( // 5 | Officially create the funtoken mapping funtoken = evm.FunToken{ - Erc20Addr: erc20, + Erc20Addr: eth.NewHexAddr(erc20), BankDenom: bankDenom, IsMadeFromCoin: false, } diff --git a/x/evm/keeper/funtoken_from_erc20_test.go b/x/evm/keeper/funtoken_from_erc20_test.go new file mode 100644 index 000000000..3a29be7b5 --- /dev/null +++ b/x/evm/keeper/funtoken_from_erc20_test.go @@ -0,0 +1,222 @@ +// Copyright (c) 2023-2024 Nibi, Inc. +package keeper_test + +import ( + "fmt" + "math/big" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/suite" + + "github.com/NibiruChain/nibiru/v2/eth" + "github.com/NibiruChain/nibiru/v2/x/common/testutil" + "github.com/NibiruChain/nibiru/v2/x/common/testutil/testapp" + "github.com/NibiruChain/nibiru/v2/x/evm" + "github.com/NibiruChain/nibiru/v2/x/evm/embeds" + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" + "github.com/NibiruChain/nibiru/v2/x/evm/keeper" + "github.com/NibiruChain/nibiru/v2/x/evm/precompile" +) + +func (s *FunTokenFromErc20Suite) TestCreateFunTokenFromERC20() { + deps := evmtest.NewTestDeps() + + // assert that the ERC20 contract is not deployed + expectedERC20Addr := crypto.CreateAddress(deps.Sender.EthAddr, deps.StateDB().GetNonce(deps.Sender.EthAddr)) + _, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, expectedERC20Addr) + s.Error(err) + + s.T().Log("Deploy ERC20") + { + metadata := keeper.ERC20Metadata{ + Name: "erc20name", + Symbol: "TOKEN", + Decimals: 18, + } + deployResp, err := evmtest.DeployContract( + &deps, embeds.SmartContract_ERC20Minter, + metadata.Name, metadata.Symbol, metadata.Decimals, + ) + s.Require().NoError(err) + s.Require().Equal(expectedERC20Addr, deployResp.ContractAddr) + + info, err := deps.EvmKeeper.FindERC20Metadata(deps.Ctx, deployResp.ContractAddr) + s.Require().NoError(err, info) + s.Require().Equal(metadata, info) + + queryCodeReq := &evm.QueryCodeRequest{ + Address: expectedERC20Addr.String(), + } + _, err = deps.EvmKeeper.Code(deps.Ctx, queryCodeReq) + s.Require().NoError(err) + } + + erc20Addr := eth.NewHexAddr(expectedERC20Addr) + + s.T().Log("happy: CreateFunToken for the ERC20") + { + s.Require().NoError(testapp.FundAccount( + deps.App.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx), + )) + + resp, err := deps.EvmKeeper.CreateFunToken( + sdk.WrapSDKContext(deps.Ctx), + &evm.MsgCreateFunToken{ + FromErc20: &erc20Addr, + Sender: deps.Sender.NibiruAddr.String(), + }, + ) + s.Require().NoError(err, "erc20 %s", erc20Addr) + + expectedBankDenom := fmt.Sprintf("erc20/%s", expectedERC20Addr.String()) + s.Equal( + resp.FuntokenMapping, + evm.FunToken{ + Erc20Addr: erc20Addr, + BankDenom: expectedBankDenom, + IsMadeFromCoin: false, + }) + + // Event "EventFunTokenCreated" must present + testutil.RequireContainsTypedEvent( + s.T(), + deps.Ctx, + &evm.EventFunTokenCreated{ + BankDenom: expectedBankDenom, + Erc20ContractAddress: erc20Addr.String(), + Creator: deps.Sender.NibiruAddr.String(), + IsMadeFromCoin: false, + }, + ) + } + + s.T().Log("sad: CreateFunToken for the ERC20: already registered") + { + // Give the sender funds for the fee + s.Require().NoError(testapp.FundAccount( + deps.App.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx), + )) + + _, err = deps.EvmKeeper.CreateFunToken( + sdk.WrapSDKContext(deps.Ctx), + &evm.MsgCreateFunToken{ + FromErc20: &erc20Addr, + Sender: deps.Sender.NibiruAddr.String(), + }, + ) + s.ErrorContains(err, "funtoken mapping already created") + } + + s.T().Log("sad: CreateFunToken for the ERC20: invalid sender") + { + _, err = deps.EvmKeeper.CreateFunToken( + sdk.WrapSDKContext(deps.Ctx), + &evm.MsgCreateFunToken{ + FromErc20: &erc20Addr, + }, + ) + s.ErrorContains(err, "invalid sender") + } + + s.T().Log("sad: CreateFunToken for the ERC20: missing erc20 address") + { + _, err = deps.EvmKeeper.CreateFunToken( + sdk.WrapSDKContext(deps.Ctx), + &evm.MsgCreateFunToken{ + FromErc20: nil, + FromBankDenom: "", + Sender: deps.Sender.NibiruAddr.String(), + }, + ) + s.ErrorContains(err, "either the \"from_erc20\" or \"from_bank_denom\" must be set") + } +} + +func (s *FunTokenFromErc20Suite) TestSendFromEvmToCosmos() { + deps := evmtest.NewTestDeps() + + var erc20Addr eth.HexAddr + s.T().Log("Deploy ERC20") + + metadata := keeper.ERC20Metadata{ + Name: "erc20name", + Symbol: "TOKEN", + Decimals: 18, + } + deployResp, err := evmtest.DeployContract( + &deps, embeds.SmartContract_ERC20Minter, + metadata.Name, metadata.Symbol, metadata.Decimals, + ) + s.Require().NoError(err) + erc20Addr = eth.NewHexAddr(deployResp.ContractAddr) + + s.T().Log("happy: CreateFunToken for the ERC20") + + s.Require().NoError(testapp.FundAccount( + deps.App.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + deps.EvmKeeper.FeeForCreateFunToken(deps.Ctx), + )) + + _, err = deps.EvmKeeper.CreateFunToken( + sdk.WrapSDKContext(deps.Ctx), + &evm.MsgCreateFunToken{ + FromErc20: &erc20Addr, + Sender: deps.Sender.NibiruAddr.String(), + }, + ) + s.Require().NoError(err, "erc20 %s", erc20Addr) + // bankDemon := resp.FuntokenMapping.BankDenom + + s.T().Logf("mint erc20 tokens to %s", deps.Sender.EthAddr.String()) + input, err := embeds.SmartContract_ERC20Minter.ABI.Pack("mint", deps.Sender.EthAddr, big.NewInt(69_420)) + s.Require().NoError(err) + erc20 := erc20Addr.ToAddr() + _, err = deps.EvmKeeper.CallContractWithInput( + deps.Ctx, deps.Sender.EthAddr, &erc20, true, input, + ) + s.Require().NoError(err) + + s.T().Log("send erc20 tokens to cosmos") + randomAcc := testutil.AccAddress() + callArgs := []any{erc20Addr.ToAddr(), big.NewInt(1), randomAcc.String()} + input, err = embeds.SmartContract_FunToken.ABI.Pack(string(precompile.FunTokenMethod_BankSend), callArgs...) + s.Require().NoError(err) + + _, err = deps.EvmKeeper.CallContractWithInput( + deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_FunToken, true, input, + ) + s.Require().NoError(err) + + s.T().Log("check balances") + evmtest.AssertERC20BalanceEqual(s.T(), deps, erc20, deps.Sender.EthAddr, big.NewInt(69_419)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, erc20, evm.EVM_MODULE_ADDRESS, big.NewInt(1)) + s.Equal("1", + deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, "erc20/"+erc20Addr.String()).Amount.String(), + ) + + // s.T().Log("send cosmos tokens back to erc20") + // _, err = deps.EvmKeeper.SendFunTokenToEvm(sdk.WrapSDKContext(deps.Ctx), &evm.MsgSendFunTokenToEvm{ + // ToEthAddr: eth.NewHexAddr(deps.Sender.EthAddr), + // Sender: randomAcc.String(), + // BankCoin: sdk.NewCoin("erc20/"+erc20Addr.String(), sdk.NewInt(1)), + // }) + // s.Require().NoError(err) +} + +type FunTokenFromErc20Suite struct { + suite.Suite +} + +func TestFunTokenFromErc20Suite(t *testing.T) { + suite.Run(t, new(FunTokenFromErc20Suite)) +} diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 0ddc2de45..584ff21bf 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -12,6 +12,5 @@ type Suite struct { // TestSuite: Runs all the tests in the suite. func TestSuite(t *testing.T) { - s := new(Suite) - suite.Run(t, s) + suite.Run(t, new(Suite)) } diff --git a/x/evm/keeper/msg_ethereum_tx_test.go b/x/evm/keeper/msg_ethereum_tx_test.go index f1075a854..374f06c57 100644 --- a/x/evm/keeper/msg_ethereum_tx_test.go +++ b/x/evm/keeper/msg_ethereum_tx_test.go @@ -125,7 +125,7 @@ func (s *Suite) TestMsgEthereumTx_ExecuteContract() { ) s.Require().NoError(err) deployResp, err := evmtest.DeployContract( - &deps, embeds.SmartContract_TestERC20, s.T(), + &deps, embeds.SmartContract_TestERC20, ) s.Require().NoError(err) contractAddr := deployResp.ContractAddr diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 3bb2cec17..7e8f7f0ec 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -20,10 +20,9 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" - "github.com/NibiruChain/nibiru/v2/x/evm/embeds" - "github.com/NibiruChain/nibiru/v2/eth" "github.com/NibiruChain/nibiru/v2/x/evm" + "github.com/NibiruChain/nibiru/v2/x/evm/embeds" "github.com/NibiruChain/nibiru/v2/x/evm/statedb" ) @@ -204,22 +203,6 @@ func (k *Keeper) ApplyEvmTx( return res, nil } -// ApplyEvmMsgWithEmptyTxConfig; Computes new state by applyig the EVM -// message to the given state. This function calls [Keeper.ApplyEvmMsg] with -// and empty`statedb.TxConfig`. -// See [Keeper.ApplyEvmMsg]. -func (k *Keeper) ApplyEvmMsgWithEmptyTxConfig( - ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool, -) (*evm.MsgEthereumTxResponse, error) { - cfg, err := k.GetEVMConfig(ctx, ctx.BlockHeader().ProposerAddress, k.EthChainID(ctx)) - if err != nil { - return nil, errors.Wrap(err, "failed to load evm config") - } - - txConfig := statedb.NewEmptyTxConfig(gethcommon.BytesToHash(ctx.HeaderHash())) - return k.ApplyEvmMsg(ctx, msg, tracer, commit, cfg, txConfig) -} - // NewEVM generates a go-ethereum VM. // // Args: @@ -517,7 +500,7 @@ func (k *Keeper) CreateFunToken( emptyErc20 := msg.FromErc20 == nil || msg.FromErc20.Size() == 0 switch { case !emptyErc20 && msg.FromBankDenom == "": - funtoken, err = k.CreateFunTokenFromERC20(ctx, *msg.FromErc20) + funtoken, err = k.CreateFunTokenFromERC20(ctx, msg.FromErc20.ToAddr()) case emptyErc20 && msg.FromBankDenom != "": funtoken, err = k.CreateFunTokenFromCoin(ctx, msg.FromBankDenom) default: diff --git a/x/evm/msg.go b/x/evm/msg.go index 4e101f52a..02b2732e7 100644 --- a/x/evm/msg.go +++ b/x/evm/msg.go @@ -489,25 +489,21 @@ func (m MsgCreateFunToken) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{addr} } -func errMsgCreateFunTokenValidate(errMsg string) error { - return fmt.Errorf("MsgCreateFunToken ValidateBasic error: %s", errMsg) -} - // ValidateBasic does a sanity check of the provided data func (m *MsgCreateFunToken) ValidateBasic() error { if _, err := sdk.AccAddressFromBech32(m.Sender); err != nil { - return errMsgCreateFunTokenValidate("invalid sender addr") + return fmt.Errorf("invalid sender addr") } - erc20 := m.FromErc20 - bankDenom := m.FromBankDenom - emptyBankDenom := bankDenom == "" - emptyErc20 := !(erc20 != nil && erc20.Size() > 0) - if (emptyErc20 && emptyBankDenom) || (!emptyErc20 && !emptyBankDenom) { - return errMsgCreateFunTokenValidate(fmt.Sprintf( - "Either the \"from_erc20\" or \"from_bank_denom\" must be set (but not both)."+ - "got values (from_erc20=\"%s\", from_bank_denom=\"%s\")", erc20, bankDenom, - )) + emptyBankDenom := m.FromBankDenom == "" + emptyErc20 := m.FromErc20 == nil || m.FromErc20.Size() == 0 + + if emptyErc20 && emptyBankDenom { + return fmt.Errorf("either the \"from_erc20\" or \"from_bank_denom\" must be set") + } + + if !emptyErc20 && !emptyBankDenom { + return fmt.Errorf("either the \"from_erc20\" or \"from_bank_denom\" must be set (but not both)") } return nil @@ -524,17 +520,13 @@ func (m MsgSendFunTokenToEvm) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{addr} } -func errMsgSendFunTokenToEvmValidate(errMsg string) error { - return fmt.Errorf("MsgSendFunTokenToEvm ValidateBasic error: %s", errMsg) -} - // ValidateBasic does a sanity check of the provided data func (m *MsgSendFunTokenToEvm) ValidateBasic() error { if _, err := sdk.AccAddressFromBech32(m.Sender); err != nil { - return errMsgCreateFunTokenValidate("invalid sender addr") + return fmt.Errorf("invalid sender addr") } if m.ToEthAddr == "" { - return errMsgSendFunTokenToEvmValidate("\"to_eth_addr\" must be set") + return fmt.Errorf("empty to_eth_addr") } return nil } diff --git a/x/evm/msg_test.go b/x/evm/msg_test.go index f6835e0d7..c673f6453 100644 --- a/x/evm/msg_test.go +++ b/x/evm/msg_test.go @@ -9,20 +9,17 @@ import ( "testing" sdkmath "cosmossdk.io/math" - "github.com/stretchr/testify/suite" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - - "github.com/NibiruChain/nibiru/v2/eth/crypto/ethsecp256k1" - - authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + "github.com/stretchr/testify/suite" "github.com/NibiruChain/nibiru/v2/app" + "github.com/NibiruChain/nibiru/v2/eth/crypto/ethsecp256k1" "github.com/NibiruChain/nibiru/v2/eth/encoding" "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 59eb53e73..88407bd11 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -22,10 +22,10 @@ var _ vm.PrecompiledContract = (*precompileFunToken)(nil) // Precompile address for "FunToken.sol", the contract that // enables transfers of ERC20 tokens to "nibi" addresses as bank coins // using the ERC20's `FunToken` mapping. -var PrecompileAddr_FuntokenGateway = gethcommon.HexToAddress("0x0000000000000000000000000000000000000800") +var PrecompileAddr_FunToken = gethcommon.HexToAddress("0x0000000000000000000000000000000000000800") func (p precompileFunToken) Address() gethcommon.Address { - return PrecompileAddr_FuntokenGateway + return PrecompileAddr_FunToken } func (p precompileFunToken) RequiredGas(input []byte) (gasPrice uint64) { @@ -131,23 +131,20 @@ func (p precompileFunToken) bankSend( // Amount should be positive if amount == nil || amount.Cmp(big.NewInt(0)) != 1 { - err = fmt.Errorf("transfer amount must be positive") - return + return nil, fmt.Errorf("transfer amount must be positive") } // The "to" argument must be a valid Nibiru address toAddr, err := sdk.AccAddressFromBech32(to) if err != nil { - err = fmt.Errorf("\"to\" is not a valid address (%s): %w", to, err) - return + return nil, fmt.Errorf("\"to\" is not a valid address (%s): %w", to, err) } // Caller transfers ERC20 to the EVM account transferTo := evm.EVM_MODULE_ADDRESS _, err = p.EvmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx) if err != nil { - err = fmt.Errorf("failed to send from caller to the EVM account: %w", err) - return + return nil, fmt.Errorf("failed to send from caller to the EVM account: %w", err) } // EVM account mints FunToken.BankDenom to module account @@ -155,18 +152,16 @@ func (p precompileFunToken) bankSend( coins := sdk.NewCoins(sdk.NewCoin(funtoken.BankDenom, amt)) err = p.BankKeeper.MintCoins(ctx, evm.ModuleName, coins) if err != nil { - err = fmt.Errorf("mint failed for module \"%s\" (%s): contract caller %s: %w", + return nil, fmt.Errorf("mint failed for module \"%s\" (%s): contract caller %s: %w", evm.ModuleName, evm.EVM_MODULE_ADDRESS.Hex(), caller.Hex(), err, ) - return } err = p.BankKeeper.SendCoinsFromModuleToAccount(ctx, evm.ModuleName, toAddr, coins) if err != nil { - err = fmt.Errorf("send failed for module \"%s\" (%s): contract caller %s: %w", + return nil, fmt.Errorf("send failed for module \"%s\" (%s): contract caller %s: %w", evm.ModuleName, evm.EVM_MODULE_ADDRESS.Hex(), caller.Hex(), err, ) - return } // If the FunToken mapping was created from a bank coin, then the EVM account diff --git a/x/evm/precompile/funtoken_test.go b/x/evm/precompile/funtoken_test.go index 239940e17..a38cf8954 100644 --- a/x/evm/precompile/funtoken_test.go +++ b/x/evm/precompile/funtoken_test.go @@ -21,63 +21,45 @@ type Suite struct { // TestPrecompileSuite: Runs all the tests in the suite. func TestSuite(t *testing.T) { - s := new(Suite) - suite.Run(t, s) -} - -func (s *Suite) TestPrecompile_FunToken() { - s.Run("PrecompileExists", s.FunToken_PrecompileExists) - s.Run("HappyPath", s.FunToken_HappyPath) + suite.Run(t, new(Suite)) } // PrecompileExists: An integration test showing that a "PrecompileError" occurs // when calling the FunToken -func (s *Suite) FunToken_PrecompileExists() { - precompileAddr := precompile.PrecompileAddr_FuntokenGateway +func (s *Suite) TestPrecompileExists() { abi := embeds.SmartContract_FunToken.ABI deps := evmtest.NewTestDeps() codeResp, err := deps.EvmKeeper.Code( sdk.WrapSDKContext(deps.Ctx), &evm.QueryCodeRequest{ - Address: precompileAddr.String(), + Address: precompile.PrecompileAddr_FunToken.String(), }, ) - s.NoError(err) + s.Require().NoError(err) s.Equal(string(codeResp.Code), "") - s.True(deps.EvmKeeper.IsAvailablePrecompile(precompileAddr), + s.True(deps.EvmKeeper.IsAvailablePrecompile(precompile.PrecompileAddr_FunToken), "did not see precompile address during \"InitPrecompiles\"") callArgs := []any{"nonsense", "args here", "to see if", "precompile is", "called"} - methodName := string(precompile.FunTokenMethod_BankSend) - packedArgs, err := abi.Pack(methodName, callArgs...) - if err != nil { - err = fmt.Errorf("failed to pack ABI args: %w", err) // easier to read - } - s.ErrorContains( + input, err := abi.Pack(string(precompile.FunTokenMethod_BankSend), callArgs...) + s.Require().ErrorContains( err, fmt.Sprintf("argument count mismatch: got %d for 3", len(callArgs)), "callArgs: ", callArgs) + s.Require().Nil(input) - fromEvmAddr := evm.EVM_MODULE_ADDRESS - commit := true - bytecodeForCall := packedArgs _, err = deps.EvmKeeper.CallContractWithInput( - deps.Ctx, fromEvmAddr, &precompileAddr, commit, - bytecodeForCall, + deps.Ctx, evm.EVM_MODULE_ADDRESS, &precompile.PrecompileAddr_FunToken, true, + input, ) s.ErrorContains(err, "precompile error") } -func (s *Suite) FunToken_HappyPath() { - precompileAddr := precompile.PrecompileAddr_FuntokenGateway - abi := embeds.SmartContract_FunToken.ABI +func (s *Suite) TestHappyPath() { deps := evmtest.NewTestDeps() - theUser := deps.Sender.EthAddr - theEvm := evm.EVM_MODULE_ADDRESS - - s.True(deps.EvmKeeper.IsAvailablePrecompile(precompileAddr), + s.True(deps.EvmKeeper.IsAvailablePrecompile(precompile.PrecompileAddr_FunToken), "did not see precompile address during \"InitPrecompiles\"") s.T().Log("Create FunToken mapping and ERC20") @@ -86,45 +68,39 @@ 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, deps.Sender.EthAddr, big.NewInt(0)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(0)) s.T().Log("Mint tokens - Fail from non-owner") { - from := theUser - to := theUser - input, err := embeds.SmartContract_ERC20Minter.ABI.Pack("mint", to, big.NewInt(69_420)) + input, err := embeds.SmartContract_ERC20Minter.ABI.Pack("mint", deps.Sender.EthAddr, big.NewInt(69_420)) s.NoError(err) _, err = deps.EvmKeeper.CallContractWithInput( - deps.Ctx, from, &contract, true, input, + deps.Ctx, deps.Sender.EthAddr, &contract, true, input, ) s.ErrorContains(err, "Ownable: caller is not the owner") } s.T().Log("Mint tokens - Success") { - from := theEvm - to := theUser - input, err := embeds.SmartContract_ERC20Minter.ABI.Pack("mint", to, big.NewInt(69_420)) + input, err := embeds.SmartContract_ERC20Minter.ABI.Pack("mint", deps.Sender.EthAddr, big.NewInt(69_420)) s.NoError(err) _, err = deps.EvmKeeper.CallContractWithInput( - deps.Ctx, from, &contract, true, input, + deps.Ctx, evm.EVM_MODULE_ADDRESS, &contract, true, 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, deps.Sender.EthAddr, big.NewInt(69_420)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(0)) } s.T().Log("Transfer - Success (sanity check)") randomAcc := testutil.AccAddress() { - from := theUser - to := theEvm - _, err := deps.EvmKeeper.ERC20().Transfer(contract, from, to, big.NewInt(1), deps.Ctx) + _, err := deps.EvmKeeper.ERC20().Transfer(contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS, 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, deps.Sender.EthAddr, big.NewInt(69_419)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(1)) s.Equal("0", deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount.String(), ) @@ -133,24 +109,22 @@ func (s *Suite) FunToken_HappyPath() { s.T().Log("Send using precompile") amtToSend := int64(419) callArgs := []any{contract, big.NewInt(amtToSend), randomAcc.String()} - methodName := string(precompile.FunTokenMethod_BankSend) - input, err := abi.Pack(methodName, callArgs...) + input, err := embeds.SmartContract_FunToken.ABI.Pack(string(precompile.FunTokenMethod_BankSend), callArgs...) s.NoError(err) - from := theUser _, err = deps.EvmKeeper.CallContractWithInput( - deps.Ctx, from, &precompileAddr, true, input, + deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_FunToken, true, 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, deps.Sender.EthAddr, big.NewInt(69_419-amtToSend)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(1)) s.Equal(fmt.Sprintf("%d", amtToSend), deps.App.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, deps.Sender.EthAddr, big.NewInt(69_000)) + evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, evm.EVM_MODULE_ADDRESS, big.NewInt(1)) s.Equal("419", deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount.String(), )