package ante_test import ( "math/big" "testing" "time" sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/client" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/crypto/tmhash" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" tmversion "github.com/cometbft/cometbft/proto/tendermint/version" "github.com/cometbft/cometbft/version" "github.com/evmos/ethermint/crypto/ethsecp256k1" "github.com/evmos/ethermint/ethereum/eip712" "github.com/evmos/ethermint/tests" etherminttypes "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" "github.com/stretchr/testify/suite" "github.com/kava-labs/kava/app" cdptypes "github.com/kava-labs/kava/x/cdp/types" evmutilkeeper "github.com/kava-labs/kava/x/evmutil/keeper" evmutiltestutil "github.com/kava-labs/kava/x/evmutil/testutil" evmutiltypes "github.com/kava-labs/kava/x/evmutil/types" hardtypes "github.com/kava-labs/kava/x/hard/types" pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types" ) const ( ChainID = app.TestChainId USDCCoinDenom = "erc20/usdc" USDCCDPType = "erc20-usdc" ) type EIP712TestSuite struct { suite.Suite tApp app.TestApp ctx sdk.Context evmutilKeeper evmutilkeeper.Keeper clientCtx client.Context ethSigner ethtypes.Signer testAddr sdk.AccAddress testAddr2 sdk.AccAddress testPrivKey cryptotypes.PrivKey testPrivKey2 cryptotypes.PrivKey testEVMAddr evmutiltypes.InternalEVMAddress testEVMAddr2 evmutiltypes.InternalEVMAddress usdcEVMAddr evmutiltypes.InternalEVMAddress } func (suite *EIP712TestSuite) getEVMAmount(amount int64) sdkmath.Int { incr := sdkmath.RelativePow(sdkmath.NewUint(10), sdkmath.NewUint(18), sdkmath.OneUint()) return sdkmath.NewInt(amount).Mul(sdkmath.NewIntFromUint64(incr.Uint64())) } func (suite *EIP712TestSuite) createTestEIP712CosmosTxBuilder( from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, msgs []sdk.Msg, ) client.TxBuilder { var err error nonce, err := suite.tApp.GetAccountKeeper().GetSequence(suite.ctx, from) suite.Require().NoError(err) pc, err := etherminttypes.ParseChainID(chainId) suite.Require().NoError(err) ethChainId := pc.Uint64() // GenerateTypedData TypedData fee := legacytx.NewStdFee(gas, gasAmount) accNumber := suite.tApp.GetAccountKeeper().GetAccount(suite.ctx, from).GetAccountNumber() data := eip712.ConstructUntypedEIP712Data(chainId, accNumber, nonce, 0, fee, msgs, "", nil) typedData, err := eip712.WrapTxToTypedData(ethChainId, msgs, data, &eip712.FeeDelegationOptions{ FeePayer: from, }, suite.tApp.GetEvmKeeper().GetParams(suite.ctx)) suite.Require().NoError(err) sigHash, err := eip712.ComputeTypedDataHash(typedData) suite.Require().NoError(err) // Sign typedData keyringSigner := tests.NewSigner(priv) signature, pubKey, err := keyringSigner.SignByAddress(from, sigHash) suite.Require().NoError(err) signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper // Add ExtensionOptionsWeb3Tx extension var option *codectypes.Any option, err = codectypes.NewAnyWithValue(ðerminttypes.ExtensionOptionsWeb3Tx{ FeePayer: from.String(), TypedDataChainID: ethChainId, FeePayerSig: signature, }) suite.Require().NoError(err) suite.clientCtx.TxConfig.SignModeHandler() txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder) suite.Require().True(ok) builder.SetExtensionOptions(option) builder.SetFeeAmount(gasAmount) builder.SetGasLimit(gas) sigsV2 := signing.SignatureV2{ PubKey: pubKey, Data: &signing.SingleSignatureData{ SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, }, Sequence: nonce, } err = builder.SetSignatures(sigsV2) suite.Require().NoError(err) err = builder.SetMsgs(msgs...) suite.Require().NoError(err) return builder } func (suite *EIP712TestSuite) SetupTest() { tApp := app.NewTestApp() suite.tApp = tApp cdc := tApp.AppCodec() suite.evmutilKeeper = tApp.GetEvmutilKeeper() addr, privkey := tests.NewAddrKey() suite.testAddr = sdk.AccAddress(addr.Bytes()) suite.testPrivKey = privkey suite.testEVMAddr = evmutiltestutil.MustNewInternalEVMAddressFromString(addr.String()) addr2, privKey2 := tests.NewAddrKey() suite.testPrivKey2 = privKey2 suite.testAddr2 = sdk.AccAddress(addr2.Bytes()) suite.testEVMAddr2 = evmutiltestutil.MustNewInternalEVMAddressFromString(addr2.String()) encodingConfig := app.MakeEncodingConfig() suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) suite.ethSigner = ethtypes.LatestSignerForChainID(tApp.GetEvmKeeper().ChainID()) // Genesis states evmGs := evmtypes.NewGenesisState( evmtypes.NewParams( "akava", // evmDenom false, // allowedUnprotectedTxs true, // enableCreate true, // enableCall evmtypes.DefaultChainConfig(), // ChainConfig nil, // extraEIPs nil, // eip712AllowedMsgs ), nil, ) feemarketGenesis := feemarkettypes.DefaultGenesisState() feemarketGenesis.Params.EnableHeight = 1 feemarketGenesis.Params.NoBaseFee = false cdpGenState := cdptypes.DefaultGenesisState() cdpGenState.Params.GlobalDebtLimit = sdk.NewInt64Coin("usdx", 53000000000000) cdpGenState.Params.CollateralParams = cdptypes.CollateralParams{ { Denom: USDCCoinDenom, Type: USDCCDPType, LiquidationRatio: sdk.MustNewDecFromStr("1.01"), DebtLimit: sdk.NewInt64Coin("usdx", 500000000000), StabilityFee: sdk.OneDec(), AuctionSize: sdkmath.NewIntFromUint64(10000000000), LiquidationPenalty: sdk.MustNewDecFromStr("0.05"), CheckCollateralizationIndexCount: sdkmath.NewInt(10), KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"), SpotMarketID: "usdc:usd", LiquidationMarketID: "usdc:usd:30", ConversionFactor: sdkmath.NewInt(18), }, } hardGenState := hardtypes.DefaultGenesisState() hardGenState.Params.MoneyMarkets = []hardtypes.MoneyMarket{ { Denom: "usdx", BorrowLimit: hardtypes.BorrowLimit{ HasMaxLimit: true, MaximumLimit: sdk.MustNewDecFromStr("100000000000"), LoanToValue: sdk.MustNewDecFromStr("1"), }, SpotMarketID: "usdx:usd", ConversionFactor: sdkmath.NewInt(1_000_000), InterestRateModel: hardtypes.InterestRateModel{ BaseRateAPY: sdk.MustNewDecFromStr("0.05"), BaseMultiplier: sdk.MustNewDecFromStr("2"), Kink: sdk.MustNewDecFromStr("0.8"), JumpMultiplier: sdk.MustNewDecFromStr("10"), }, ReserveFactor: sdk.MustNewDecFromStr("0.05"), KeeperRewardPercentage: sdk.ZeroDec(), }, } pricefeedGenState := pricefeedtypes.DefaultGenesisState() pricefeedGenState.Params.Markets = []pricefeedtypes.Market{ { MarketID: "usdx:usd", BaseAsset: "usdx", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true, }, { MarketID: "usdc:usd", BaseAsset: "usdc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true, }, { MarketID: "usdc:usd:30", BaseAsset: "usdc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true, }, } pricefeedGenState.PostedPrices = []pricefeedtypes.PostedPrice{ { MarketID: "usdx:usd", OracleAddress: sdk.AccAddress{}, Price: sdk.MustNewDecFromStr("1.00"), Expiry: time.Now().Add(1 * time.Hour), }, { MarketID: "usdc:usd", OracleAddress: sdk.AccAddress{}, Price: sdk.MustNewDecFromStr("1.00"), Expiry: time.Now().Add(1 * time.Hour), }, { MarketID: "usdc:usd:30", OracleAddress: sdk.AccAddress{}, Price: sdk.MustNewDecFromStr("1.00"), Expiry: time.Now().Add(1 * time.Hour), }, } genState := app.GenesisState{ evmtypes.ModuleName: cdc.MustMarshalJSON(evmGs), feemarkettypes.ModuleName: cdc.MustMarshalJSON(feemarketGenesis), cdptypes.ModuleName: cdc.MustMarshalJSON(&cdpGenState), hardtypes.ModuleName: cdc.MustMarshalJSON(&hardGenState), pricefeedtypes.ModuleName: cdc.MustMarshalJSON(&pricefeedGenState), } // funds our test accounts with some ukava coinsGenState := app.NewFundedGenStateWithSameCoins( tApp.AppCodec(), sdk.NewCoins(sdk.NewInt64Coin("ukava", 1e9)), []sdk.AccAddress{suite.testAddr, suite.testAddr2}, ) tApp.InitializeFromGenesisStatesWithTimeAndChainID( time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), ChainID, genState, coinsGenState, ) // consensus key consPriv, err := ethsecp256k1.GenerateKey() suite.Require().NoError(err) consAddress := sdk.ConsAddress(consPriv.PubKey().Address()) ctx := tApp.NewContext(false, tmproto.Header{ Height: tApp.LastBlockHeight() + 1, ChainID: ChainID, Time: time.Now().UTC(), ProposerAddress: consAddress.Bytes(), Version: tmversion.Consensus{ Block: version.BlockProtocol, }, LastBlockId: tmproto.BlockID{ Hash: tmhash.Sum([]byte("block_id")), PartSetHeader: tmproto.PartSetHeader{ Total: 11, Hash: tmhash.Sum([]byte("partset_header")), }, }, AppHash: tmhash.Sum([]byte("app")), DataHash: tmhash.Sum([]byte("data")), EvidenceHash: tmhash.Sum([]byte("evidence")), ValidatorsHash: tmhash.Sum([]byte("validators")), NextValidatorsHash: tmhash.Sum([]byte("next_validators")), ConsensusHash: tmhash.Sum([]byte("consensus")), LastResultsHash: tmhash.Sum([]byte("last_result")), }) suite.ctx = ctx // We need to set the validator as calling the EVM looks up the validator address // https://github.com/evmos/ethermint/blob/f21592ebfe74da7590eb42ed926dae970b2a9a3f/x/evm/keeper/state_transition.go#L487 // evmkeeper.EVMConfig() will return error "failed to load evm config" if not set valAcc := ðerminttypes.EthAccount{ BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(consAddress.Bytes()), nil, 0, 0), CodeHash: common.BytesToHash(crypto.Keccak256(nil)).String(), } tApp.GetAccountKeeper().SetAccount(ctx, valAcc) _, testAddresses := app.GeneratePrivKeyAddressPairs(1) valAddr := sdk.ValAddress(testAddresses[0].Bytes()) validator, err := stakingtypes.NewValidator(valAddr, consPriv.PubKey(), stakingtypes.Description{}) suite.Require().NoError(err) err = tApp.GetStakingKeeper().SetValidatorByConsAddr(ctx, validator) suite.Require().NoError(err) tApp.GetStakingKeeper().SetValidator(ctx, validator) // Deploy an ERC20 contract for USDC contractAddr := suite.deployUSDCERC20(tApp, ctx) pair := evmutiltypes.NewConversionPair( contractAddr, USDCCoinDenom, ) suite.usdcEVMAddr = pair.GetAddress() // update consensus params cParams := tApp.GetConsensusParams(suite.ctx) cParams.Block.MaxGas = sims.DefaultGenTxGas * 20 tApp.StoreConsensusParams(suite.ctx, cParams) // Add a contract to evmutil conversion pair evmutilParams := suite.evmutilKeeper.GetParams(suite.ctx) evmutilParams.EnabledConversionPairs = evmutiltypes.NewConversionPairs( evmutiltypes.NewConversionPair( // First contract evmutil module deploys evmutiltestutil.MustNewInternalEVMAddressFromString("0x15932E26f5BD4923d46a2b205191C4b5d5f43FE3"), "erc20/usdc", ), ) suite.evmutilKeeper.SetParams(suite.ctx, evmutilParams) // allow msgs through evm eip712 evmKeeper := suite.tApp.GetEvmKeeper() params := evmKeeper.GetParams(suite.ctx) params.EIP712AllowedMsgs = []evmtypes.EIP712AllowedMsg{ { MsgTypeUrl: "/kava.evmutil.v1beta1.MsgConvertERC20ToCoin", MsgValueTypeName: "MsgValueEVMConvertERC20ToCoin", ValueTypes: []evmtypes.EIP712MsgAttrType{ {Name: "initiator", Type: "string"}, {Name: "receiver", Type: "string"}, {Name: "kava_erc20_address", Type: "string"}, {Name: "amount", Type: "string"}, }, }, { MsgTypeUrl: "/kava.cdp.v1beta1.MsgCreateCDP", MsgValueTypeName: "MsgValueCDPCreate", ValueTypes: []evmtypes.EIP712MsgAttrType{ {Name: "sender", Type: "string"}, {Name: "collateral", Type: "Coin"}, {Name: "principal", Type: "Coin"}, {Name: "collateral_type", Type: "string"}, }, }, { MsgTypeUrl: "/kava.cdp.v1beta1.MsgDeposit", MsgValueTypeName: "MsgValueCDPDeposit", ValueTypes: []evmtypes.EIP712MsgAttrType{ {Name: "depositor", Type: "string"}, {Name: "owner", Type: "string"}, {Name: "collateral", Type: "Coin"}, {Name: "collateral_type", Type: "string"}, }, }, { MsgTypeUrl: "/kava.hard.v1beta1.MsgDeposit", MsgValueTypeName: "MsgValueHardDeposit", ValueTypes: []evmtypes.EIP712MsgAttrType{ {Name: "depositor", Type: "string"}, {Name: "amount", Type: "Coin[]"}, }, }, { MsgTypeUrl: "/kava.evmutil.v1beta1.MsgConvertCoinToERC20", MsgValueTypeName: "MsgValueEVMConvertCoinToERC20", ValueTypes: []evmtypes.EIP712MsgAttrType{ {Name: "initiator", Type: "string"}, {Name: "receiver", Type: "string"}, {Name: "amount", Type: "Coin"}, }, }, { MsgTypeUrl: "/kava.cdp.v1beta1.MsgRepayDebt", MsgValueTypeName: "MsgValueCDPRepayDebt", ValueTypes: []evmtypes.EIP712MsgAttrType{ {Name: "sender", Type: "string"}, {Name: "collateral_type", Type: "string"}, {Name: "payment", Type: "Coin"}, }, }, { MsgTypeUrl: "/kava.hard.v1beta1.MsgWithdraw", MsgValueTypeName: "MsgValueHardWithdraw", ValueTypes: []evmtypes.EIP712MsgAttrType{ {Name: "depositor", Type: "string"}, {Name: "amount", Type: "Coin[]"}, }, }, } evmKeeper.SetParams(suite.ctx, params) // give test address 50k erc20 usdc to begin with initBal := suite.getEVMAmount(50_000) err = suite.evmutilKeeper.MintERC20( ctx, pair.GetAddress(), // contractAddr suite.testEVMAddr, //receiver initBal.BigInt(), ) suite.Require().NoError(err) err = suite.evmutilKeeper.MintERC20( ctx, pair.GetAddress(), // contractAddr suite.testEVMAddr2, //receiver initBal.BigInt(), ) suite.Require().NoError(err) // We need to commit so that the ethermint feemarket beginblock runs to set the minfee // feeMarketKeeper.GetBaseFee() will return nil otherwise suite.Commit() // set base fee suite.tApp.GetFeeMarketKeeper().SetBaseFee(suite.ctx, big.NewInt(100)) } func (suite *EIP712TestSuite) Commit() { _ = suite.tApp.Commit() header := suite.ctx.BlockHeader() header.Height += 1 suite.tApp.BeginBlock(abci.RequestBeginBlock{ Header: header, }) // update ctx suite.ctx = suite.tApp.NewContext(false, header) } func (suite *EIP712TestSuite) deployUSDCERC20(app app.TestApp, ctx sdk.Context) evmutiltypes.InternalEVMAddress { // make sure module account is created suite.tApp.FundModuleAccount( suite.ctx, evmutiltypes.ModuleName, sdk.NewCoins(sdk.NewCoin("ukava", sdkmath.NewInt(0))), ) contractAddr, err := suite.evmutilKeeper.DeployTestMintableERC20Contract(suite.ctx, "USDC", "USDC", uint8(18)) suite.Require().NoError(err) suite.Require().Greater(len(contractAddr.Address), 0) return contractAddr } func (suite *EIP712TestSuite) TestEIP712Tx() { encodingConfig := app.MakeEncodingConfig() testcases := []struct { name string usdcDepositAmt int64 usdxToMintAmt int64 updateTx func(txBuilder client.TxBuilder, msgs []sdk.Msg) client.TxBuilder updateMsgs func(msgs []sdk.Msg) []sdk.Msg expectedCode uint32 failCheckTx bool errMsg string }{ { name: "processes deposit eip712 messages successfully", usdcDepositAmt: 100, usdxToMintAmt: 99, }, { name: "fails when convertion more erc20 usdc than balance", usdcDepositAmt: 51_000, usdxToMintAmt: 100, errMsg: "transfer amount exceeds balance", }, { name: "fails when minting more usdx than allowed", usdcDepositAmt: 100, usdxToMintAmt: 100, errMsg: "proposed collateral ratio is below liquidation ratio", }, { name: "fails when trying to convert usdc for another address", usdcDepositAmt: 100, usdxToMintAmt: 90, errMsg: "unauthorized", failCheckTx: true, updateMsgs: func(msgs []sdk.Msg) []sdk.Msg { convertMsg := evmutiltypes.NewMsgConvertERC20ToCoin( suite.testEVMAddr2, suite.testAddr, suite.usdcEVMAddr, suite.getEVMAmount(100), ) msgs[0] = &convertMsg return msgs }, }, { name: "fails when trying to convert erc20 for non-whitelisted contract", usdcDepositAmt: 100, usdxToMintAmt: 90, errMsg: "ERC20 token not enabled to convert to sdk.Coin", updateMsgs: func(msgs []sdk.Msg) []sdk.Msg { convertMsg := evmutiltypes.NewMsgConvertERC20ToCoin( suite.testEVMAddr, suite.testAddr, suite.testEVMAddr2, suite.getEVMAmount(100), ) msgs[0] = &convertMsg return msgs }, }, { name: "fails when signer tries to send messages with invalid signature", usdcDepositAmt: 100, usdxToMintAmt: 90, failCheckTx: true, errMsg: "tx intended signer does not match the given signer", updateTx: func(txBuilder client.TxBuilder, msgs []sdk.Msg) client.TxBuilder { var option *codectypes.Any option, _ = codectypes.NewAnyWithValue(ðerminttypes.ExtensionOptionsWeb3Tx{ FeePayer: suite.testAddr.String(), TypedDataChainID: 2221, FeePayerSig: []byte("sig"), }) builder, _ := txBuilder.(authtx.ExtensionOptionsTxBuilder) builder.SetExtensionOptions(option) return txBuilder }, }, { name: "fails when insufficient gas fees are provided", usdcDepositAmt: 100, usdxToMintAmt: 90, errMsg: "insufficient funds", updateTx: func(txBuilder client.TxBuilder, msgs []sdk.Msg) client.TxBuilder { bk := suite.tApp.GetBankKeeper() gasCoins := bk.GetBalance(suite.ctx, suite.testAddr, "ukava") suite.tApp.GetBankKeeper().SendCoins(suite.ctx, suite.testAddr, suite.testAddr2, sdk.NewCoins(gasCoins)) return txBuilder }, }, { name: "fails when invalid chain id is provided", usdcDepositAmt: 100, usdxToMintAmt: 90, failCheckTx: true, errMsg: "invalid chain-id", updateTx: func(txBuilder client.TxBuilder, msgs []sdk.Msg) client.TxBuilder { gasAmt := sdk.NewCoins(sdk.NewCoin("ukava", sdkmath.NewInt(20))) return suite.createTestEIP712CosmosTxBuilder( suite.testAddr, suite.testPrivKey, "kavatest_12-1", uint64(sims.DefaultGenTxGas*10), gasAmt, msgs, ) }, }, { name: "fails when invalid fee payer is provided", usdcDepositAmt: 100, usdxToMintAmt: 90, failCheckTx: true, errMsg: "invalid pubkey", updateTx: func(txBuilder client.TxBuilder, msgs []sdk.Msg) client.TxBuilder { gasAmt := sdk.NewCoins(sdk.NewCoin("ukava", sdkmath.NewInt(20))) return suite.createTestEIP712CosmosTxBuilder( suite.testAddr2, suite.testPrivKey2, ChainID, uint64(sims.DefaultGenTxGas*10), gasAmt, msgs, ) }, }, } for _, tc := range testcases { suite.Run(tc.name, func() { suite.SetupTest() // create messages to convert, mint, and deposit to lend usdcAmt := suite.getEVMAmount(tc.usdcDepositAmt) convertMsg := evmutiltypes.NewMsgConvertERC20ToCoin( suite.testEVMAddr, suite.testAddr, suite.usdcEVMAddr, usdcAmt, ) usdxAmt := sdkmath.NewInt(1_000_000).Mul(sdkmath.NewInt(tc.usdxToMintAmt)) mintMsg := cdptypes.NewMsgCreateCDP( suite.testAddr, sdk.NewCoin(USDCCoinDenom, usdcAmt), sdk.NewCoin(cdptypes.DefaultStableDenom, usdxAmt), USDCCDPType, ) lendMsg := hardtypes.NewMsgDeposit( suite.testAddr, sdk.NewCoins(sdk.NewCoin(cdptypes.DefaultStableDenom, usdxAmt)), ) msgs := []sdk.Msg{ &convertMsg, &mintMsg, &lendMsg, } if tc.updateMsgs != nil { msgs = tc.updateMsgs(msgs) } gasAmt := sdk.NewCoins(sdk.NewCoin("ukava", sdkmath.NewInt(20))) txBuilder := suite.createTestEIP712CosmosTxBuilder( suite.testAddr, suite.testPrivKey, ChainID, uint64(sims.DefaultGenTxGas*10), gasAmt, msgs, ) if tc.updateTx != nil { txBuilder = tc.updateTx(txBuilder, msgs) } txBytes, err := encodingConfig.TxConfig.TxEncoder()(txBuilder.GetTx()) suite.Require().NoError(err) resCheckTx := suite.tApp.CheckTx( abci.RequestCheckTx{ Tx: txBytes, Type: abci.CheckTxType_New, }, ) if !tc.failCheckTx { suite.Require().Equal(resCheckTx.Code, uint32(0), resCheckTx.Log) } else { suite.Require().NotEqual(resCheckTx.Code, uint32(0), resCheckTx.Log) suite.Require().Contains(resCheckTx.Log, tc.errMsg) } resDeliverTx := suite.tApp.DeliverTx( abci.RequestDeliverTx{ Tx: txBytes, }, ) if tc.errMsg == "" { suite.Require().Equal(resDeliverTx.Code, uint32(0), resDeliverTx.Log) // validate user cosmos erc20/usd balance bk := suite.tApp.GetBankKeeper() amt := bk.GetBalance(suite.ctx, suite.testAddr, USDCCoinDenom) suite.Require().Equal(sdk.ZeroInt(), amt.Amount) // validate cdp cdp, found := suite.tApp.GetCDPKeeper().GetCdpByOwnerAndCollateralType(suite.ctx, suite.testAddr, USDCCDPType) suite.Require().True(found) suite.Require().Equal(suite.testAddr, cdp.Owner) suite.Require().Equal(sdk.NewCoin(USDCCoinDenom, suite.getEVMAmount(100)), cdp.Collateral) suite.Require().Equal(sdk.NewCoin("usdx", sdkmath.NewInt(99_000_000)), cdp.Principal) // validate hard hardDeposit, found := suite.tApp.GetHardKeeper().GetDeposit(suite.ctx, suite.testAddr) suite.Require().True(found) suite.Require().Equal(suite.testAddr, hardDeposit.Depositor) suite.Require().Equal(sdk.NewCoins(sdk.NewCoin("usdx", sdkmath.NewInt(99_000_000))), hardDeposit.Amount) } else { suite.Require().NotEqual(resDeliverTx.Code, uint32(0), resCheckTx.Log) suite.Require().Contains(resDeliverTx.Log, tc.errMsg) } }) } } func (suite *EIP712TestSuite) TestEIP712Tx_DepositAndWithdraw() { encodingConfig := app.MakeEncodingConfig() // create deposit msgs usdcAmt := suite.getEVMAmount(100) convertMsg := evmutiltypes.NewMsgConvertERC20ToCoin( suite.testEVMAddr, suite.testAddr, suite.usdcEVMAddr, usdcAmt, ) usdxAmt := sdkmath.NewInt(1_000_000).Mul(sdkmath.NewInt(99)) mintMsg := cdptypes.NewMsgCreateCDP( suite.testAddr, sdk.NewCoin(USDCCoinDenom, usdcAmt), sdk.NewCoin(cdptypes.DefaultStableDenom, usdxAmt), USDCCDPType, ) lendMsg := hardtypes.NewMsgDeposit( suite.testAddr, sdk.NewCoins(sdk.NewCoin(cdptypes.DefaultStableDenom, usdxAmt)), ) depositMsgs := []sdk.Msg{ &convertMsg, &mintMsg, &lendMsg, } // deliver deposit msg gasAmt := sdk.NewCoins(sdk.NewCoin("ukava", sdkmath.NewInt(20))) txBuilder := suite.createTestEIP712CosmosTxBuilder( suite.testAddr, suite.testPrivKey, ChainID, uint64(sims.DefaultGenTxGas*10), gasAmt, depositMsgs, ) txBytes, err := encodingConfig.TxConfig.TxEncoder()(txBuilder.GetTx()) suite.Require().NoError(err) resDeliverTx := suite.tApp.DeliverTx( abci.RequestDeliverTx{ Tx: txBytes, }, ) suite.Require().Equal(resDeliverTx.Code, uint32(0), resDeliverTx.Log) // validate hard hardDeposit, found := suite.tApp.GetHardKeeper().GetDeposit(suite.ctx, suite.testAddr) suite.Require().True(found) suite.Require().Equal(suite.testAddr, hardDeposit.Depositor) suite.Require().Equal(sdk.NewCoins(sdk.NewCoin("usdx", sdkmath.NewInt(99_000_000))), hardDeposit.Amount) // validate erc20 balance coinBal, err := suite.evmutilKeeper.QueryERC20BalanceOf(suite.ctx, suite.usdcEVMAddr, suite.testEVMAddr) suite.Require().NoError(err) suite.Require().Equal(suite.getEVMAmount(49_900).BigInt(), coinBal) // withdraw msgs withdrawConvertMsg := evmutiltypes.NewMsgConvertCoinToERC20( suite.testAddr.String(), suite.testEVMAddr.String(), sdk.NewCoin(USDCCoinDenom, usdcAmt), ) cdpWithdrawMsg := cdptypes.NewMsgRepayDebt( suite.testAddr, USDCCDPType, sdk.NewCoin(cdptypes.DefaultStableDenom, usdxAmt), ) hardWithdrawMsg := hardtypes.NewMsgWithdraw( suite.testAddr, sdk.NewCoins(sdk.NewCoin(cdptypes.DefaultStableDenom, usdxAmt)), ) withdrawMsgs := []sdk.Msg{ &hardWithdrawMsg, &cdpWithdrawMsg, &withdrawConvertMsg, } // deliver withdraw msg txBuilder = suite.createTestEIP712CosmosTxBuilder( suite.testAddr, suite.testPrivKey, ChainID, uint64(sims.DefaultGenTxGas*10), gasAmt, withdrawMsgs, ) txBytes, err = encodingConfig.TxConfig.TxEncoder()(txBuilder.GetTx()) suite.Require().NoError(err) resDeliverTx = suite.tApp.DeliverTx( abci.RequestDeliverTx{ Tx: txBytes, }, ) suite.Require().Equal(resDeliverTx.Code, uint32(0), resDeliverTx.Log) // validate hard & cdp should be repayed _, found = suite.tApp.GetHardKeeper().GetDeposit(suite.ctx, suite.testAddr) suite.Require().False(found) _, found = suite.tApp.GetCDPKeeper().GetCdpByOwnerAndCollateralType(suite.ctx, suite.testAddr, USDCCDPType) suite.Require().False(found) // validate user cosmos erc20/usd balance bk := suite.tApp.GetBankKeeper() amt := bk.GetBalance(suite.ctx, suite.testAddr, USDCCoinDenom) suite.Require().Equal(sdk.ZeroInt(), amt.Amount) // validate erc20 balance coinBal, err = suite.evmutilKeeper.QueryERC20BalanceOf(suite.ctx, suite.usdcEVMAddr, suite.testEVMAddr) suite.Require().NoError(err) suite.Require().Equal(suite.getEVMAmount(50_000).BigInt(), coinBal) } func TestEIP712Suite(t *testing.T) { suite.Run(t, new(EIP712TestSuite)) }