mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 09:37:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			300 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			300 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package keeper_test
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"math/big"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"github.com/stretchr/testify/suite"
 | 
						|
 | 
						|
	sdkmath "cosmossdk.io/math"
 | 
						|
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						|
 | 
						|
	"github.com/0glabs/0g-chain/app"
 | 
						|
	"github.com/0glabs/0g-chain/x/evmutil/testutil"
 | 
						|
	"github.com/0glabs/0g-chain/x/evmutil/types"
 | 
						|
)
 | 
						|
 | 
						|
type convertCosmosCoinToERC20Suite struct {
 | 
						|
	testutil.Suite
 | 
						|
}
 | 
						|
 | 
						|
func TestConversionCosmosNativeToEvmSuite(t *testing.T) {
 | 
						|
	suite.Run(t, new(convertCosmosCoinToERC20Suite))
 | 
						|
}
 | 
						|
 | 
						|
// fail test if contract for denom not registered
 | 
						|
func (suite *convertCosmosCoinToERC20Suite) denomContractRegistered(denom string) types.InternalEVMAddress {
 | 
						|
	contractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
 | 
						|
	suite.True(found)
 | 
						|
	return contractAddress
 | 
						|
}
 | 
						|
 | 
						|
// fail test if contract for denom IS registered
 | 
						|
func (suite *convertCosmosCoinToERC20Suite) denomContractNotRegistered(denom string) {
 | 
						|
	_, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom)
 | 
						|
	suite.False(found)
 | 
						|
}
 | 
						|
 | 
						|
// more tests of tests of this method are made to the msg handler, see ./msg_server_test.go
 | 
						|
func (suite *convertCosmosCoinToERC20Suite) TestConvertCosmosCoinToERC20() {
 | 
						|
	allowedDenom := "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2"
 | 
						|
	initialFunding := sdk.NewInt64Coin(allowedDenom, int64(1e10))
 | 
						|
	initiator := app.RandomAddress()
 | 
						|
 | 
						|
	amount := sdk.NewInt64Coin(allowedDenom, 6e8)
 | 
						|
	receiver1 := types.BytesToInternalEVMAddress(app.RandomAddress().Bytes())
 | 
						|
	receiver2 := types.BytesToInternalEVMAddress(app.RandomAddress().Bytes())
 | 
						|
 | 
						|
	var contractAddress types.InternalEVMAddress
 | 
						|
 | 
						|
	caller, key := testutil.RandomEvmAccount()
 | 
						|
	query := func(method string, args ...interface{}) ([]interface{}, error) {
 | 
						|
		return suite.QueryContract(
 | 
						|
			types.ERC20KavaWrappedCosmosCoinContract.ABI,
 | 
						|
			caller,
 | 
						|
			key,
 | 
						|
			contractAddress,
 | 
						|
			method,
 | 
						|
			args...,
 | 
						|
		)
 | 
						|
	}
 | 
						|
	checkTotalSupply := func(expectedSupply sdkmath.Int) {
 | 
						|
		res, err := query("totalSupply")
 | 
						|
		suite.NoError(err)
 | 
						|
		suite.Len(res, 1)
 | 
						|
		suite.BigIntsEqual(expectedSupply.BigInt(), res[0].(*big.Int), "unexpected total supply")
 | 
						|
	}
 | 
						|
	checkBalanceOf := func(address types.InternalEVMAddress, expectedBalance sdkmath.Int) {
 | 
						|
		res, err := query("balanceOf", address.Address)
 | 
						|
		suite.NoError(err)
 | 
						|
		suite.Len(res, 1)
 | 
						|
		suite.BigIntsEqual(expectedBalance.BigInt(), res[0].(*big.Int), fmt.Sprintf("unexpected balanceOf for %s", address))
 | 
						|
	}
 | 
						|
 | 
						|
	suite.SetupTest()
 | 
						|
 | 
						|
	suite.Run("fails when denom not allowed", func() {
 | 
						|
		suite.denomContractNotRegistered(allowedDenom)
 | 
						|
		err := suite.Keeper.ConvertCosmosCoinToERC20(
 | 
						|
			suite.Ctx,
 | 
						|
			initiator,
 | 
						|
			receiver1,
 | 
						|
			sdk.NewCoin(allowedDenom, sdkmath.NewInt(6e8)),
 | 
						|
		)
 | 
						|
		suite.ErrorContains(err, "sdk.Coin not enabled to convert to ERC20 token")
 | 
						|
		suite.denomContractNotRegistered(allowedDenom)
 | 
						|
	})
 | 
						|
 | 
						|
	suite.Run("allowed denoms have contract deploys on first conversion", func() {
 | 
						|
		// make the denom allowed for conversion
 | 
						|
		params := suite.Keeper.GetParams(suite.Ctx)
 | 
						|
		params.AllowedCosmosDenoms = types.NewAllowedCosmosCoinERC20Tokens(
 | 
						|
			types.NewAllowedCosmosCoinERC20Token(allowedDenom, "Kava EVM Atom", "ATOM", 6),
 | 
						|
		)
 | 
						|
		suite.Keeper.SetParams(suite.Ctx, params)
 | 
						|
 | 
						|
		// fund account
 | 
						|
		err := suite.App.FundAccount(suite.Ctx, initiator, sdk.NewCoins(initialFunding))
 | 
						|
		suite.NoError(err, "failed to initially fund account")
 | 
						|
 | 
						|
		// first conversion
 | 
						|
		err = suite.Keeper.ConvertCosmosCoinToERC20(
 | 
						|
			suite.Ctx,
 | 
						|
			initiator,
 | 
						|
			receiver1,
 | 
						|
			sdk.NewCoin(allowedDenom, sdkmath.NewInt(6e8)),
 | 
						|
		)
 | 
						|
		suite.NoError(err)
 | 
						|
 | 
						|
		// contract should be deployed & registered
 | 
						|
		contractAddress = suite.denomContractRegistered(allowedDenom)
 | 
						|
 | 
						|
		// sdk coin deducted from initiator
 | 
						|
		expectedBalance := initialFunding.Sub(amount)
 | 
						|
		balance := suite.BankKeeper.GetBalance(suite.Ctx, initiator, allowedDenom)
 | 
						|
		suite.Equal(expectedBalance, balance)
 | 
						|
 | 
						|
		// erc20 minted to receiver
 | 
						|
		checkBalanceOf(receiver1, amount.Amount)
 | 
						|
		// total supply of erc20 should have increased
 | 
						|
		checkTotalSupply(amount.Amount)
 | 
						|
 | 
						|
		// event should be emitted
 | 
						|
		suite.EventsContains(suite.GetEvents(),
 | 
						|
			sdk.NewEvent(
 | 
						|
				types.EventTypeConvertCosmosCoinToERC20,
 | 
						|
				sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()),
 | 
						|
				sdk.NewAttribute(types.AttributeKeyReceiver, receiver1.String()),
 | 
						|
				sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddress.Hex()),
 | 
						|
				sdk.NewAttribute(types.AttributeKeyAmount, amount.String()),
 | 
						|
			),
 | 
						|
		)
 | 
						|
	})
 | 
						|
 | 
						|
	suite.Run("2nd deploy uses same contract", func() {
 | 
						|
		// expect no initial balance
 | 
						|
		checkBalanceOf(receiver2, sdkmath.NewInt(0))
 | 
						|
 | 
						|
		// 2nd conversion
 | 
						|
		err := suite.Keeper.ConvertCosmosCoinToERC20(
 | 
						|
			suite.Ctx,
 | 
						|
			initiator,
 | 
						|
			receiver2,
 | 
						|
			sdk.NewCoin(allowedDenom, sdkmath.NewInt(6e8)),
 | 
						|
		)
 | 
						|
		suite.NoError(err)
 | 
						|
 | 
						|
		// contract address should not change
 | 
						|
		convertTwiceContractAddress := suite.denomContractRegistered(allowedDenom)
 | 
						|
		suite.Equal(contractAddress, convertTwiceContractAddress)
 | 
						|
 | 
						|
		// sdk coin deducted from initiator
 | 
						|
		expectedBalance := initialFunding.Sub(amount).Sub(amount)
 | 
						|
		balance := suite.BankKeeper.GetBalance(suite.Ctx, initiator, allowedDenom)
 | 
						|
		suite.Equal(expectedBalance, balance)
 | 
						|
 | 
						|
		// erc20 minted to receiver
 | 
						|
		checkBalanceOf(receiver2, amount.Amount)
 | 
						|
		// total supply of erc20 should have increased
 | 
						|
		checkTotalSupply(amount.Amount.MulRaw(2))
 | 
						|
 | 
						|
		// event should be emitted
 | 
						|
		suite.EventsContains(suite.GetEvents(),
 | 
						|
			sdk.NewEvent(
 | 
						|
				types.EventTypeConvertCosmosCoinToERC20,
 | 
						|
				sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()),
 | 
						|
				sdk.NewAttribute(types.AttributeKeyReceiver, receiver2.String()),
 | 
						|
				sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddress.Hex()),
 | 
						|
				sdk.NewAttribute(types.AttributeKeyAmount, amount.String()),
 | 
						|
			),
 | 
						|
		)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
type convertCosmosCoinFromERC20Suite struct {
 | 
						|
	testutil.Suite
 | 
						|
 | 
						|
	denom     string
 | 
						|
	initiator types.InternalEVMAddress
 | 
						|
	receiver  sdk.AccAddress
 | 
						|
 | 
						|
	contractAddress types.InternalEVMAddress
 | 
						|
	initialPosition sdk.Coin
 | 
						|
 | 
						|
	query func(method string, args ...interface{}) ([]interface{}, error)
 | 
						|
}
 | 
						|
 | 
						|
func (suite *convertCosmosCoinFromERC20Suite) SetupTest() {
 | 
						|
	var err error
 | 
						|
	suite.Suite.SetupTest()
 | 
						|
 | 
						|
	suite.denom = "magic"
 | 
						|
	suite.initiator = testutil.RandomInternalEVMAddress()
 | 
						|
	suite.receiver = app.RandomAddress()
 | 
						|
 | 
						|
	// manually create an initial position - sdk coin locked in module
 | 
						|
	suite.initialPosition = sdk.NewInt64Coin(suite.denom, 1e12)
 | 
						|
	err = suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(suite.initialPosition))
 | 
						|
	suite.NoError(err)
 | 
						|
 | 
						|
	// deploy erc20 contract for the denom
 | 
						|
	tokenInfo := types.AllowedCosmosCoinERC20Token{
 | 
						|
		CosmosDenom: suite.denom,
 | 
						|
		Name:        "Test Token",
 | 
						|
		Symbol:      "MAGIC",
 | 
						|
		Decimals:    6,
 | 
						|
	}
 | 
						|
	suite.contractAddress, err = suite.Keeper.GetOrDeployCosmosCoinERC20Contract(suite.Ctx, tokenInfo)
 | 
						|
	suite.NoError(err)
 | 
						|
 | 
						|
	// manually create an initial position - minted tokens
 | 
						|
	err = suite.Keeper.MintERC20(suite.Ctx, suite.contractAddress, suite.initiator, suite.initialPosition.Amount.BigInt())
 | 
						|
	suite.NoError(err)
 | 
						|
 | 
						|
	caller, key := testutil.RandomEvmAccount()
 | 
						|
	suite.query = func(method string, args ...interface{}) ([]interface{}, error) {
 | 
						|
		return suite.QueryContract(
 | 
						|
			types.ERC20KavaWrappedCosmosCoinContract.ABI,
 | 
						|
			caller,
 | 
						|
			key,
 | 
						|
			suite.contractAddress,
 | 
						|
			method,
 | 
						|
			args...,
 | 
						|
		)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (suite *convertCosmosCoinFromERC20Suite) checkTotalSupply(expectedSupply sdkmath.Int) {
 | 
						|
	res, err := suite.query("totalSupply")
 | 
						|
	suite.NoError(err)
 | 
						|
	suite.Len(res, 1)
 | 
						|
	suite.BigIntsEqual(expectedSupply.BigInt(), res[0].(*big.Int), "unexpected total supply")
 | 
						|
}
 | 
						|
 | 
						|
func (suite *convertCosmosCoinFromERC20Suite) checkBalanceOf(address types.InternalEVMAddress, expectedBalance sdkmath.Int) {
 | 
						|
	res, err := suite.query("balanceOf", address.Address)
 | 
						|
	suite.NoError(err)
 | 
						|
	suite.Len(res, 1)
 | 
						|
	suite.BigIntsEqual(expectedBalance.BigInt(), res[0].(*big.Int), fmt.Sprintf("unexpected balanceOf for %s", address))
 | 
						|
}
 | 
						|
 | 
						|
func TestConversionCosmosNativeFromEVMSuite(t *testing.T) {
 | 
						|
	suite.Run(t, new(convertCosmosCoinFromERC20Suite))
 | 
						|
}
 | 
						|
 | 
						|
func (suite *convertCosmosCoinFromERC20Suite) TestConvertCosmosCoinFromERC20_NoContractDeployed() {
 | 
						|
	err := suite.Keeper.ConvertCosmosCoinFromERC20(
 | 
						|
		suite.Ctx,
 | 
						|
		suite.initiator,
 | 
						|
		suite.receiver,
 | 
						|
		sdk.NewInt64Coin("unsupported-denom", 1e6),
 | 
						|
	)
 | 
						|
	suite.ErrorContains(err, "no erc20 contract found for unsupported-denom")
 | 
						|
}
 | 
						|
 | 
						|
func (suite *convertCosmosCoinFromERC20Suite) TestConvertCosmosCoinFromERC20() {
 | 
						|
	// half the initial position
 | 
						|
	amount := suite.initialPosition.SubAmount(suite.initialPosition.Amount.QuoRaw(2))
 | 
						|
 | 
						|
	suite.Run("partial withdraw", func() {
 | 
						|
		err := suite.Keeper.ConvertCosmosCoinFromERC20(
 | 
						|
			suite.Ctx,
 | 
						|
			suite.initiator,
 | 
						|
			suite.receiver,
 | 
						|
			amount,
 | 
						|
		)
 | 
						|
		suite.NoError(err)
 | 
						|
 | 
						|
		suite.checkTotalSupply(amount.Amount)
 | 
						|
		suite.checkBalanceOf(suite.initiator, amount.Amount)
 | 
						|
		suite.App.CheckBalance(suite.T(), suite.Ctx, suite.receiver, sdk.NewCoins(amount))
 | 
						|
	})
 | 
						|
 | 
						|
	suite.Run("full withdraw", func() {
 | 
						|
		err := suite.Keeper.ConvertCosmosCoinFromERC20(
 | 
						|
			suite.Ctx,
 | 
						|
			suite.initiator,
 | 
						|
			suite.receiver,
 | 
						|
			amount,
 | 
						|
		)
 | 
						|
		suite.NoError(err)
 | 
						|
 | 
						|
		// expect no remaining erc20 balance
 | 
						|
		suite.checkTotalSupply(sdkmath.ZeroInt())
 | 
						|
		suite.checkBalanceOf(suite.initiator, sdkmath.ZeroInt())
 | 
						|
		// expect full amount withdrawn to receiver
 | 
						|
		suite.App.CheckBalance(suite.T(), suite.Ctx, suite.receiver, sdk.NewCoins(suite.initialPosition))
 | 
						|
	})
 | 
						|
 | 
						|
	suite.Run("insufficient balance", func() {
 | 
						|
		err := suite.Keeper.ConvertCosmosCoinFromERC20(
 | 
						|
			suite.Ctx,
 | 
						|
			suite.initiator,
 | 
						|
			suite.receiver,
 | 
						|
			amount,
 | 
						|
		)
 | 
						|
		suite.ErrorContains(err, "failed to convert to cosmos coins: insufficient funds")
 | 
						|
	})
 | 
						|
}
 |