package keeper_test

import (
	"math/big"
	"testing"

	"github.com/stretchr/testify/suite"

	sdkmath "cosmossdk.io/math"
	sdk "github.com/cosmos/cosmos-sdk/types"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/math"

	"github.com/0glabs/0g-chain/app"
	"github.com/0glabs/0g-chain/x/evmutil/keeper"
	"github.com/0glabs/0g-chain/x/evmutil/testutil"
	"github.com/0glabs/0g-chain/x/evmutil/types"
)

type MsgServerSuite struct {
	testutil.Suite

	msgServer types.MsgServer
}

func (suite *MsgServerSuite) SetupTest() {
	suite.Suite.SetupTest()
	suite.msgServer = keeper.NewMsgServerImpl(suite.App.GetEvmutilKeeper())
}

func TestMsgServerSuite(t *testing.T) {
	suite.Run(t, new(MsgServerSuite))
}

func (suite *MsgServerSuite) TestConvertCoinToERC20() {
	invoker, err := sdk.AccAddressFromBech32("0g1ffv7nhd3z6sych2qpqkk03ec6hzkmufyhp5hf8")
	suite.Require().NoError(err)

	err = suite.App.FundAccount(suite.Ctx, invoker, sdk.NewCoins(sdk.NewCoin("erc20/usdc", sdkmath.NewInt(10000))))
	suite.Require().NoError(err)

	contractAddr := suite.DeployERC20()

	pair := types.NewConversionPair(
		contractAddr,
		"erc20/usdc",
	)

	// Module account should have starting balance
	pairStartingBal := big.NewInt(10000)
	err = suite.Keeper.MintERC20(
		suite.Ctx,
		pair.GetAddress(), // contractAddr
		types.NewInternalEVMAddress(types.ModuleEVMAddress), //receiver
		pairStartingBal,
	)
	suite.Require().NoError(err)

	type errArgs struct {
		expectPass bool
		contains   string
	}

	tests := []struct {
		name    string
		msg     types.MsgConvertCoinToERC20
		errArgs errArgs
	}{
		{
			"valid",
			types.NewMsgConvertCoinToERC20(
				invoker.String(),
				"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
				sdk.NewCoin("erc20/usdc", sdkmath.NewInt(1234)),
			),
			errArgs{
				expectPass: true,
			},
		},
		{
			"invalid - odd length hex address",
			types.NewMsgConvertCoinToERC20(
				invoker.String(),
				"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc",
				sdk.NewCoin("erc20/usdc", sdkmath.NewInt(1234)),
			),
			errArgs{
				expectPass: false,
				contains:   "invalid Receiver address: string is not a hex address",
			},
		},
		// Amount coin is not validated by msg_server, but on msg itself
	}

	for _, tc := range tests {
		suite.Run(tc.name, func() {
			_, err := suite.msgServer.ConvertCoinToERC20(sdk.WrapSDKContext(suite.Ctx), &tc.msg)

			if tc.errArgs.expectPass {
				suite.Require().NoError(err)

				bal := suite.GetERC20BalanceOf(
					types.ERC20MintableBurnableContract.ABI,
					pair.GetAddress(),
					testutil.MustNewInternalEVMAddressFromString(tc.msg.Receiver),
				)

				suite.Require().Equal(tc.msg.Amount.Amount.BigInt(), bal, "balance should match converted amount")

				// msg server event
				suite.EventsContains(suite.GetEvents(),
					sdk.NewEvent(
						sdk.EventTypeMessage,
						sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
						sdk.NewAttribute(sdk.AttributeKeySender, tc.msg.Initiator),
					))

				// keeper event
				suite.EventsContains(suite.GetEvents(),
					sdk.NewEvent(
						types.EventTypeConvertCoinToERC20,
						sdk.NewAttribute(types.AttributeKeyInitiator, tc.msg.Initiator),
						sdk.NewAttribute(types.AttributeKeyReceiver, tc.msg.Receiver),
						sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()),
						sdk.NewAttribute(types.AttributeKeyAmount, tc.msg.Amount.String()),
					))
			} else {
				suite.Require().Error(err)
				suite.Require().Contains(err.Error(), tc.errArgs.contains)
			}
		})
	}
}

func (suite *MsgServerSuite) TestConvertERC20ToCoin() {
	contractAddr := suite.DeployERC20()
	pair := types.NewConversionPair(
		contractAddr,
		"erc20/usdc",
	)

	// give invoker account some erc20 usdc to begin with
	invoker := testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
	pairStartingBal := big.NewInt(10_000_000)
	err := suite.Keeper.MintERC20(
		suite.Ctx,
		pair.GetAddress(), // contractAddr
		invoker,           //receiver
		pairStartingBal,
	)
	suite.Require().NoError(err)

	invokerCosmosAddr, err := sdk.AccAddressFromHexUnsafe(invoker.String()[2:])
	suite.Require().NoError(err)

	// create user account, otherwise `CallEVMWithData` will fail due to failing to get user account when finding its sequence.
	err = suite.App.FundAccount(suite.Ctx, invokerCosmosAddr, sdk.NewCoins(sdk.NewCoin(pair.Denom, sdk.ZeroInt())))
	suite.Require().NoError(err)

	type errArgs struct {
		expectPass bool
		contains   string
	}

	tests := []struct {
		name           string
		msg            types.MsgConvertERC20ToCoin
		approvalAmount *big.Int
		errArgs        errArgs
	}{
		{
			"valid",
			types.NewMsgConvertERC20ToCoin(
				invoker,
				invokerCosmosAddr,
				contractAddr,
				sdkmath.NewInt(10_000),
			),
			math.MaxBig256,
			errArgs{
				expectPass: true,
			},
		},
		{
			"invalid - invalid hex address",
			types.MsgConvertERC20ToCoin{
				Initiator:           "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc",
				Receiver:            invokerCosmosAddr.String(),
				ZgChainERC20Address: contractAddr.String(),
				Amount:              sdkmath.NewInt(10_000),
			},
			math.MaxBig256,
			errArgs{
				expectPass: false,
				contains:   "invalid initiator address: string is not a hex address",
			},
		},
		{
			"invalid - insufficient coins",
			types.NewMsgConvertERC20ToCoin(
				invoker,
				invokerCosmosAddr,
				contractAddr,
				sdkmath.NewIntFromBigInt(pairStartingBal).Add(sdk.OneInt()),
			),
			math.MaxBig256,
			errArgs{
				expectPass: false,
				contains:   "transfer amount exceeds balance",
			},
		},
		{
			"invalid - contract address",
			types.NewMsgConvertERC20ToCoin(
				invoker,
				invokerCosmosAddr,
				testutil.MustNewInternalEVMAddressFromString("0x7Bbf300890857b8c241b219C6a489431669b3aFA"),
				sdkmath.NewInt(10_000),
			),
			math.MaxBig256,
			errArgs{
				expectPass: false,
				contains:   "ERC20 token not enabled to convert to sdk.Coin",
			},
		},
	}

	for _, tc := range tests {
		suite.Run(tc.name, func() {
			_, err := suite.msgServer.ConvertERC20ToCoin(sdk.WrapSDKContext(suite.Ctx), &tc.msg)

			if tc.errArgs.expectPass {
				suite.Require().NoError(err)

				// validate user balance after conversion
				bal := suite.GetERC20BalanceOf(
					types.ERC20MintableBurnableContract.ABI,
					pair.GetAddress(),
					testutil.MustNewInternalEVMAddressFromString(tc.msg.Initiator),
				)
				expectedBal := sdkmath.NewIntFromBigInt(pairStartingBal).Sub(tc.msg.Amount)
				suite.Require().Equal(expectedBal.BigInt(), bal, "user erc20 balance is invalid")

				// validate user coin balance
				coinBal := suite.App.GetBankKeeper().GetBalance(suite.Ctx, invokerCosmosAddr, pair.Denom)
				suite.Require().Equal(tc.msg.Amount, coinBal.Amount, "user coin balance is invalid")

				// msg server event
				suite.EventsContains(suite.GetEvents(),
					sdk.NewEvent(
						sdk.EventTypeMessage,
						sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
						sdk.NewAttribute(sdk.AttributeKeySender, tc.msg.Initiator),
					))

				// keeper event
				suite.EventsContains(suite.GetEvents(),
					sdk.NewEvent(
						types.EventTypeConvertERC20ToCoin,
						sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()),
						sdk.NewAttribute(types.AttributeKeyInitiator, tc.msg.Initiator),
						sdk.NewAttribute(types.AttributeKeyReceiver, tc.msg.Receiver),
						sdk.NewAttribute(types.AttributeKeyAmount, sdk.NewCoin(pair.Denom, tc.msg.Amount).String()),
					))
			} else {
				suite.Require().Error(err)
				suite.Require().Contains(err.Error(), tc.errArgs.contains)
			}
		})
	}
}

func (suite *MsgServerSuite) TestConvertCosmosCoinToERC20_InitialContractDeploy() {
	allowedDenom := "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2"
	initialFunding := int64(1e10)
	fundedAccount := app.RandomAddress()

	setup := func() {
		suite.SetupTest()

		// make the denom allowed for conversion
		params := suite.Keeper.GetParams(suite.Ctx)
		params.AllowedCosmosDenoms = types.NewAllowedCosmosCoinERC20Tokens(
			types.NewAllowedCosmosCoinERC20Token(allowedDenom, "0gChain EVM Atom", "ATOM", 6),
		)
		suite.Keeper.SetParams(suite.Ctx, params)

		// fund account
		err := suite.App.FundAccount(suite.Ctx, fundedAccount, sdk.NewCoins(
			sdk.NewInt64Coin(allowedDenom, initialFunding),
		))
		suite.NoError(err, "failed to initially fund account")
	}

	testCases := []struct {
		name            string
		msg             types.MsgConvertCosmosCoinToERC20
		amountConverted sdkmath.Int
		expectedErr     string
	}{
		{
			name: "valid - first conversion deploys contract, send to self",
			msg: types.NewMsgConvertCosmosCoinToERC20(
				fundedAccount.String(),
				common.BytesToAddress(fundedAccount.Bytes()).Hex(), // it's me!
				sdk.NewInt64Coin(allowedDenom, 5e7),
			),
			amountConverted: sdkmath.NewInt(5e7),
			expectedErr:     "",
		},
		{
			name: "valid - first conversion deploys contract, send to other",
			msg: types.NewMsgConvertCosmosCoinToERC20(
				fundedAccount.String(),
				testutil.RandomEvmAddress().Hex(), // someone else!
				sdk.NewInt64Coin(allowedDenom, 9993317),
			),
			amountConverted: sdkmath.NewInt(9993317),
			expectedErr:     "",
		},
		{
			name: "invalid - un-allowed denom",
			msg: types.NewMsgConvertCosmosCoinToERC20(
				app.RandomAddress().String(),
				testutil.RandomEvmAddress().Hex(),
				sdk.NewInt64Coin("not-allowed-denom", 1e4),
			),
			expectedErr: "sdk.Coin not enabled to convert to ERC20 token",
		},
		{
			name: "invalid - bad initiator",
			msg: types.NewMsgConvertCosmosCoinToERC20(
				"invalid-0g-address",
				testutil.RandomEvmAddress().Hex(),
				sdk.NewInt64Coin(allowedDenom, 1e4),
			),
			expectedErr: "invalid initiator address",
		},
		{
			name: "invalid - bad receiver",
			msg: types.NewMsgConvertCosmosCoinToERC20(
				app.RandomAddress().String(),
				"invalid-0x-address",
				sdk.NewInt64Coin(allowedDenom, 1e4),
			),
			expectedErr: "invalid receiver address",
		},
		{
			name: "invalid - bad receiver",
			msg: types.NewMsgConvertCosmosCoinToERC20(
				app.RandomAddress().String(),
				"invalid-0x-address",
				sdk.NewInt64Coin(allowedDenom, 1e4),
			),
			expectedErr: "invalid receiver address",
		},
		{
			name: "invalid - insufficient balance",
			msg: types.NewMsgConvertCosmosCoinToERC20(
				fundedAccount.String(),
				testutil.RandomEvmAddress().Hex(),
				sdk.NewInt64Coin(allowedDenom, initialFunding+1),
			),
			expectedErr: "insufficient funds",
		},
		// NOTE: a zero amount tx passes in this scope but will fail to pass ValidateBasic()
	}

	for _, tc := range testCases {
		suite.Run(tc.name, func() {
			// initial setup
			setup()

			moduleBalanceBefore := suite.ModuleBalance(allowedDenom)

			// submit message
			_, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &tc.msg)

			// verify error, if expected
			if tc.expectedErr != "" {
				suite.ErrorContains(err, tc.expectedErr)
				// the contract wasn't previously deployed, so still shouldn't be
				_, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, allowedDenom)
				suite.False(found)
				return
			}

			// verify success
			suite.NoError(err)

			initiator := sdk.MustAccAddressFromBech32(tc.msg.Initiator)
			receiver := testutil.MustNewInternalEVMAddressFromString(tc.msg.Receiver)

			// initiator no longer has sdk coins
			cosmosBalanceAfter := suite.BankKeeper.GetBalance(suite.Ctx, initiator, allowedDenom)
			suite.Equal(
				sdkmath.NewInt(initialFunding).Sub(tc.amountConverted),
				cosmosBalanceAfter.Amount,
				"unexpected sdk.Coin balance of initiator",
			)

			// sdk coins are locked into module
			moduleBalanceAfter := suite.ModuleBalance(allowedDenom)
			suite.Equal(
				moduleBalanceBefore.Add(tc.amountConverted),
				moduleBalanceAfter,
				"unexpected module balance",
			)

			// deployed contract address is registered in module store
			contractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, allowedDenom)
			suite.True(found, "expected deployed contract address to be registered, found none")

			// receiver has been minted correct number of tokens
			erc20Balance, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, receiver)
			suite.NoError(err)
			suite.Equal(tc.amountConverted.BigInt(), erc20Balance, "unexpected erc20 balance for receiver")

			// msg server event
			suite.EventsContains(suite.GetEvents(),
				sdk.NewEvent(
					sdk.EventTypeMessage,
					sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
					sdk.NewAttribute(sdk.AttributeKeySender, initiator.String()),
				))

			// keeper event
			suite.EventsContains(suite.GetEvents(),
				sdk.NewEvent(
					types.EventTypeConvertCosmosCoinToERC20,
					sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()),
					sdk.NewAttribute(types.AttributeKeyReceiver, receiver.String()),
					sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddress.Hex()),
					sdk.NewAttribute(types.AttributeKeyAmount, tc.msg.Amount.String()),
				))
		})
	}
}

func (suite *MsgServerSuite) TestConvertCosmosCoinToERC20_AlreadyDeployedContract() {
	allowedDenom := "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2"
	initialFunding := int64(1e10)
	fundedAccount := app.RandomAddress()

	amount := sdkmath.NewInt(6e8)
	receiver1 := types.BytesToInternalEVMAddress(app.RandomAddress().Bytes())
	receiver2 := types.BytesToInternalEVMAddress(app.RandomAddress().Bytes())

	suite.SetupTest()

	// make the denom allowed for conversion
	params := suite.Keeper.GetParams(suite.Ctx)
	params.AllowedCosmosDenoms = types.NewAllowedCosmosCoinERC20Tokens(
		types.NewAllowedCosmosCoinERC20Token(allowedDenom, "0gChain EVM Atom", "ATOM", 6),
	)
	suite.Keeper.SetParams(suite.Ctx, params)

	// fund account
	err := suite.App.FundAccount(suite.Ctx, fundedAccount, sdk.NewCoins(
		sdk.NewInt64Coin(allowedDenom, initialFunding),
	))
	suite.NoError(err, "failed to initially fund account")

	// verify contract is not deployed
	_, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, allowedDenom)
	suite.False(found)

	// initial convert deploys contract
	msg := types.NewMsgConvertCosmosCoinToERC20(
		fundedAccount.String(),
		receiver1.Hex(),
		sdk.NewCoin(allowedDenom, amount),
	)
	_, err = suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg)
	suite.NoError(err)

	contractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, allowedDenom)
	suite.True(found)

	// second convert uses same contract
	msg.Receiver = receiver2.Hex()
	_, err = suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg)
	suite.NoError(err)

	after2ndUseAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, allowedDenom)
	suite.True(found)
	suite.Equal(contractAddress, after2ndUseAddress, "contract address should remain the same")

	// check balances
	bal1, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, receiver1)
	suite.NoError(err)
	suite.Equal(amount.BigInt(), bal1)

	bal2, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, receiver2)
	suite.NoError(err)
	suite.Equal(amount.BigInt(), bal2)

	// check total supply
	caller, key := testutil.RandomEvmAccount()
	totalSupply, err := suite.QueryContract(
		types.ERC20ZgChainWrappedCosmosCoinContract.ABI,
		caller,
		key,
		contractAddress,
		"totalSupply",
	)
	suite.NoError(err)
	suite.Len(totalSupply, 1)
	suite.Equal(amount.MulRaw(2).BigInt(), totalSupply[0].(*big.Int))
}

func (suite *MsgServerSuite) TestConvertCosmosCoinFromERC20() {
	denom := "magic"
	tokenInfo := types.NewAllowedCosmosCoinERC20Token(denom, "Cosmos Coin", "MAGIC", 6)
	initialPosition := sdk.NewInt64Coin(denom, 1e10)
	initiator := testutil.RandomInternalEVMAddress()

	var contractAddress types.InternalEVMAddress
	setup := func() {
		suite.SetupTest()

		// allow conversion to the denom
		params := suite.Keeper.GetParams(suite.Ctx)
		params.AllowedCosmosDenoms = append(params.AllowedCosmosDenoms, tokenInfo)
		suite.Keeper.SetParams(suite.Ctx, params)

		// setup initial position
		addr := app.RandomAddress()
		err := suite.App.FundAccount(suite.Ctx, addr, sdk.NewCoins(initialPosition))
		suite.NoError(err)
		err = suite.Keeper.ConvertCosmosCoinToERC20(suite.Ctx, addr, initiator, initialPosition)
		suite.NoError(err)

		contractAddress, _ = suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
	}

	testCases := []struct {
		name            string
		msg             types.MsgConvertCosmosCoinFromERC20
		amountConverted sdkmath.Int
		expectedErr     string
	}{
		{
			name: "valid - full convert",
			msg: types.NewMsgConvertCosmosCoinFromERC20(
				initiator.Hex(),
				app.RandomAddress().String(),
				initialPosition,
			),
			amountConverted: initialPosition.Amount,
			expectedErr:     "",
		},
		{
			name: "valid - partial convert",
			msg: types.NewMsgConvertCosmosCoinFromERC20(
				initiator.Hex(),
				app.RandomAddress().String(),
				sdk.NewInt64Coin(denom, 123456),
			),
			amountConverted: sdkmath.NewInt(123456),
			expectedErr:     "",
		},
		{
			name: "invalid - bad initiator",
			msg: types.NewMsgConvertCosmosCoinFromERC20(
				"invalid-address",
				app.RandomAddress().String(),
				sdk.NewInt64Coin(denom, 123456),
			),
			amountConverted: sdkmath.ZeroInt(),
			expectedErr:     "invalid initiator address",
		},
		{
			name: "invalid - bad receiver",
			msg: types.NewMsgConvertCosmosCoinFromERC20(
				testutil.RandomEvmAddress().Hex(),
				"invalid-address",
				sdk.NewInt64Coin(denom, 123456),
			),
			amountConverted: sdkmath.ZeroInt(),
			expectedErr:     "invalid receiver address",
		},
		{
			name: "invalid - unsupported asset",
			msg: types.NewMsgConvertCosmosCoinFromERC20(
				initiator.Hex(),
				app.RandomAddress().String(),
				sdk.NewInt64Coin("not-supported", 123456),
			),
			amountConverted: sdkmath.ZeroInt(),
			expectedErr:     "no erc20 contract found",
		},
		{
			name: "invalid - insufficient funds",
			msg: types.NewMsgConvertCosmosCoinFromERC20(
				initiator.Hex(),
				app.RandomAddress().String(),
				initialPosition.AddAmount(sdkmath.OneInt()),
			),
			amountConverted: sdkmath.ZeroInt(),
			expectedErr:     "failed to convert to cosmos coins: insufficient funds",
		},
	}

	for _, tc := range testCases {
		suite.Run(tc.name, func() {
			setup()

			_, err := suite.msgServer.ConvertCosmosCoinFromERC20(suite.Ctx, &tc.msg)

			if tc.expectedErr != "" {
				suite.ErrorContains(err, tc.expectedErr)
				// expect no change in erc20 balance
				balance, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, initiator)
				suite.NoError(err)
				suite.BigIntsEqual(initialPosition.Amount.BigInt(), balance, "expected no change in initiator's erc20 balance")
				// expect no change in module balance
				suite.Equal(initialPosition.Amount, suite.ModuleBalance(denom), "expected no change in module balance")
				return
			}

			suite.NoError(err)

			receiver := sdk.MustAccAddressFromBech32(tc.msg.Receiver)
			// expect receiver to have the sdk coins
			sdkBalance := suite.BankKeeper.GetBalance(suite.Ctx, receiver, denom)
			suite.Equal(tc.amountConverted, sdkBalance.Amount)

			newEvmBalance := initialPosition.SubAmount(tc.amountConverted)
			// expect initiator to have the balance deducted
			evmBalance, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, initiator)
			suite.NoError(err)
			suite.BigIntsEqual(newEvmBalance.Amount.BigInt(), evmBalance, "unexpected initiator final erc20 balance")

			// expect tokens to be deducted from module account
			suite.True(newEvmBalance.Amount.Equal(suite.ModuleBalance(denom)), "unexpected module balance")

			// expect erc20 total supply to reflect new value
			caller, key := testutil.RandomEvmAccount()
			totalSupply, err := suite.QueryContract(
				types.ERC20ZgChainWrappedCosmosCoinContract.ABI,
				caller,
				key,
				contractAddress,
				"totalSupply",
			)
			suite.NoError(err)
			suite.BigIntsEqual(newEvmBalance.Amount.BigInt(), totalSupply[0].(*big.Int), "unexpected total supply")
		})
	}
}

// the test verifies the behavior for when a denom is removed from the params list
// after conversions have been made:
// - it should prevent more conversions from sdk -> evm for that denom
// - existing erc20s should be allowed to get converted back to a sdk.Coins
// - allowing the denom again should use existing contract
func (suite *MsgServerSuite) TestConvertCosmosCoinForRemovedDenom() {
	denom := "magic"
	tokenInfo := types.NewAllowedCosmosCoinERC20Token(denom, "MAGIC COIN", "MAGIC", 6)
	account := app.RandomAddress()
	evmAddr := types.BytesToInternalEVMAddress(account.Bytes())
	coin := func(amt int64) sdk.Coin { return sdk.NewInt64Coin(denom, amt) }

	// fund account
	suite.NoError(suite.App.FundAccount(suite.Ctx, account, sdk.NewCoins(coin(1e10))))

	// setup the token as allowed
	params := suite.Keeper.GetParams(suite.Ctx)
	params.AllowedCosmosDenoms = append(params.AllowedCosmosDenoms, tokenInfo)
	suite.Keeper.SetParams(suite.Ctx, params)

	// convert some coins while its allowed
	msg := types.NewMsgConvertCosmosCoinToERC20(account.String(), evmAddr.Hex(), coin(5e9))
	_, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg)
	suite.NoError(err)

	// expect contract registered
	contractAddress, isRegistered := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
	suite.True(isRegistered)
	suite.False(contractAddress.IsNil())

	// unregister contract
	params.AllowedCosmosDenoms = []types.AllowedCosmosCoinERC20Token{}
	suite.Keeper.SetParams(suite.Ctx, params)

	suite.Run("disallows sdk -> evm when removed", func() {
		msg := types.NewMsgConvertCosmosCoinToERC20(account.String(), evmAddr.Hex(), coin(5e9))
		_, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg)
		suite.ErrorContains(err, "sdk.Coin not enabled to convert to ERC20 token")
	})

	suite.Run("allows conversion of existing ERC20s", func() {
		msg := types.NewMsgConvertCosmosCoinFromERC20(evmAddr.Hex(), account.String(), coin(5e9))
		_, err := suite.msgServer.ConvertCosmosCoinFromERC20(suite.Ctx, &msg)
		suite.NoError(err)

		// should be fully withdrawn
		erc20Bal, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, evmAddr)
		suite.NoError(err)
		suite.BigIntsEqual(big.NewInt(0), erc20Bal, "cosmos coins were not converted back")
		sdkBal := suite.BankKeeper.GetBalance(suite.Ctx, account, denom)
		suite.Equal(coin(1e10), sdkBal)
	})

	suite.Run("contract stays registered", func() {
		postDisableContractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
		suite.True(found)
		suite.Equal(contractAddress, postDisableContractAddress)
	})

	suite.Run("re-enable uses original contract", func() {
		// re-enable contract
		params.AllowedCosmosDenoms = append(params.AllowedCosmosDenoms, tokenInfo)
		suite.Keeper.SetParams(suite.Ctx, params)

		// attempt conversion
		msg := types.NewMsgConvertCosmosCoinToERC20(account.String(), evmAddr.Hex(), coin(1e10))
		_, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg)
		suite.NoError(err)

		// should have balance on original ERC20 contract
		erc20Bal, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, evmAddr)
		suite.NoError(err)
		suite.BigIntsEqual(big.NewInt(1e10), erc20Bal, "cosmos coins were not converted")
		sdkBal := suite.BankKeeper.GetBalance(suite.Ctx, account, denom)
		suite.True(sdkBal.IsZero())
	})
}