mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-04-04 15:55:23 +00:00 
			
		
		
		
	* Implement bep3 evm native conversion logic
* Update changelog
* Fix indentation
* Add bep3 conversion keeper tests
* make DefaultBEP3ConversionDenoms private
* refactor bep3 conversion
* update bep3 tests to cover all bep3 assets
* minor refactor
(cherry picked from commit 3afb656d1f)
Co-authored-by: Draco <draco@dracoli.com>
			
			
This commit is contained in:
		
							parent
							
								
									7f73061c13
								
							
						
					
					
						commit
						a548de05c2
					
				| @ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ | |||||||
| - (validator-vesting) [#1832] Add grpc query service to replace removed legacy querier | - (validator-vesting) [#1832] Add grpc query service to replace removed legacy querier | ||||||
| - (incentive) [#1836] Update x/incentive cli to use grpc query client | - (incentive) [#1836] Update x/incentive cli to use grpc query client | ||||||
| - (ibc) [#1839] Add ibc packet forward middleware for ibc transfers | - (ibc) [#1839] Add ibc packet forward middleware for ibc transfers | ||||||
|  | - (evmutil) [#1848] Update evm native conversion logic to handle bep3 assets | ||||||
| 
 | 
 | ||||||
| ## [v0.25.0] | ## [v0.25.0] | ||||||
| 
 | 
 | ||||||
| @ -328,6 +329,7 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md). | |||||||
|   large-scale simulations remotely using aws-batch |   large-scale simulations remotely using aws-batch | ||||||
| 
 | 
 | ||||||
| [#1846]: https://github.com/Kava-Labs/kava/pull/1846 | [#1846]: https://github.com/Kava-Labs/kava/pull/1846 | ||||||
|  | [#1848]: https://github.com/Kava-Labs/kava/pull/1848 | ||||||
| [#1839]: https://github.com/Kava-Labs/kava/pull/1839 | [#1839]: https://github.com/Kava-Labs/kava/pull/1839 | ||||||
| [#1836]: https://github.com/Kava-Labs/kava/pull/1836 | [#1836]: https://github.com/Kava-Labs/kava/pull/1836 | ||||||
| [#1832]: https://github.com/Kava-Labs/kava/pull/1832 | [#1832]: https://github.com/Kava-Labs/kava/pull/1832 | ||||||
|  | |||||||
| @ -71,7 +71,15 @@ func (k Keeper) ConvertCoinToERC20( | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := k.UnlockERC20Tokens(ctx, pair, coin.Amount.BigInt(), receiverAccount); err != nil { | 	// handle bep3 conversion pair (8 decimals sdk.Coin -> 18 decimals erc20)
 | ||||||
|  | 	// when converting bep3 bank coins to erc20, we will need to unlock the 18
 | ||||||
|  | 	// decimals erc20 equivalent of the 8 decimals bep3 sdk.Coins
 | ||||||
|  | 	amountToUnlock := coin.Amount.BigInt() | ||||||
|  | 	if isBep3Asset(pair.Denom) { | ||||||
|  | 		amountToUnlock = convertBep3CoinAmountToERC20Amount(coin.Amount.BigInt()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := k.UnlockERC20Tokens(ctx, pair, amountToUnlock, receiverAccount); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -102,13 +110,23 @@ func (k Keeper) ConvertERC20ToCoin( | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	amountToLock := amount.BigInt() | ||||||
|  | 	amountToMint := amount.BigInt() | ||||||
|  | 
 | ||||||
|  | 	if isBep3Asset(pair.Denom) { | ||||||
|  | 		amountToMint, amountToLock, err = bep3ERC20AmountToCoinMintAndERC20LockAmount(amount.BigInt()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// lock erc20 tokens
 | 	// lock erc20 tokens
 | ||||||
| 	if err := k.LockERC20Tokens(ctx, pair, amount.BigInt(), initiator); err != nil { | 	if err := k.LockERC20Tokens(ctx, pair, amountToLock, initiator); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// mint conversion pair coin
 | 	// mint conversion pair coin
 | ||||||
| 	coin, err := k.MintConversionPairCoin(ctx, pair, amount.BigInt(), receiver) | 	coin, err := k.MintConversionPairCoin(ctx, pair, amountToMint, receiver) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | |||||||
							
								
								
									
										55
									
								
								x/evmutil/keeper/conversion_evm_native_bep3.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								x/evmutil/keeper/conversion_evm_native_bep3.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | package keeper | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math/big" | ||||||
|  | 
 | ||||||
|  | 	errorsmod "cosmossdk.io/errors" | ||||||
|  | 	"github.com/kava-labs/kava/x/evmutil/types" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	bep3Denoms = map[string]bool{ | ||||||
|  | 		"bnb":  true, | ||||||
|  | 		"busd": true, | ||||||
|  | 		"btcb": true, | ||||||
|  | 		"xrpb": true, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bep3ConversionFactor = new(big.Int).Exp(big.NewInt(10), big.NewInt(10), nil) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func isBep3Asset(denom string) bool { | ||||||
|  | 	return bep3Denoms[denom] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // convertBep3CoinAmountToERC20Amount converts a bep3 coin amount with 8 decimals
 | ||||||
|  | // to the equivalent ERC20 token with 18 decimals.
 | ||||||
|  | func convertBep3CoinAmountToERC20Amount(amount *big.Int) (erc20Amount *big.Int) { | ||||||
|  | 	return new(big.Int).Mul(amount, bep3ConversionFactor) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // convertBep3ERC20AmountToCoinAmount converts a bep3 ERC20 token with 18 decimals
 | ||||||
|  | // to the equivalent coin amount with 8 decimals, and dropping the remainder.
 | ||||||
|  | func convertBep3ERC20AmountToCoinAmount(amount *big.Int) (coinAmount *big.Int) { | ||||||
|  | 	return new(big.Int).Div(amount, bep3ConversionFactor) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // bep3ERC20AmountToCoinMintAndERC20LockAmount converts 18 decimals erc20 bep3
 | ||||||
|  | // amount to the equivalent 8 decimals coin amount to mint, and the
 | ||||||
|  | // 18 decimals erc20 bep3 amount to lock for the converted coin amount.
 | ||||||
|  | func bep3ERC20AmountToCoinMintAndERC20LockAmount(amount *big.Int) ( | ||||||
|  | 	amountToMint *big.Int, amountToLock *big.Int, err error, | ||||||
|  | ) { | ||||||
|  | 	amountToMint = convertBep3ERC20AmountToCoinAmount(amount) | ||||||
|  | 
 | ||||||
|  | 	// make sure we have at least 1 sdk.Coin to mint
 | ||||||
|  | 	if amountToMint.Cmp(big.NewInt(0)) == 0 { | ||||||
|  | 		err := errorsmod.Wrapf( | ||||||
|  | 			types.ErrInsufficientConversionAmount, | ||||||
|  | 			"unable to convert bep3 coin due converting less than 1 native unit", | ||||||
|  | 		) | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 	amountToLock = convertBep3CoinAmountToERC20Amount(amountToMint) | ||||||
|  | 	return amountToMint, amountToLock, nil | ||||||
|  | } | ||||||
							
								
								
									
										415
									
								
								x/evmutil/keeper/conversion_evm_native_bep3_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										415
									
								
								x/evmutil/keeper/conversion_evm_native_bep3_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,415 @@ | |||||||
|  | package keeper_test | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	sdkmath "cosmossdk.io/math" | ||||||
|  | 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||||
|  | 	"github.com/stretchr/testify/suite" | ||||||
|  | 
 | ||||||
|  | 	"github.com/kava-labs/kava/x/evmutil/testutil" | ||||||
|  | 	"github.com/kava-labs/kava/x/evmutil/types" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Bep3ConversionTestSuite struct { | ||||||
|  | 	testutil.Suite | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	bep3Denoms = []string{"bnb", "busd", "btcb", "xrpb"} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestBep3ConversionTestSuite(t *testing.T) { | ||||||
|  | 	suite.Run(t, new(Bep3ConversionTestSuite)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *Bep3ConversionTestSuite) TestConvertCoinToERC20_Bep3() { | ||||||
|  | 	for _, denom := range bep3Denoms { | ||||||
|  | 		suite.testConvertBep3CoinToERC20(denom) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *Bep3ConversionTestSuite) TestConvertERC20ToCoin_Bep3() { | ||||||
|  | 	for _, denom := range bep3Denoms { | ||||||
|  | 		suite.testConvertBep3ERC20ToCoin(denom) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *Bep3ConversionTestSuite) setEnabledConversionPairDenom(denom string) { | ||||||
|  | 	params := suite.Keeper.GetParams(suite.Ctx) | ||||||
|  | 	params.EnabledConversionPairs[0].Denom = denom | ||||||
|  | 	suite.Keeper.SetParams(suite.Ctx, params) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *Bep3ConversionTestSuite) testConvertBep3CoinToERC20(denom string) { | ||||||
|  | 	invoker, err := sdk.AccAddressFromBech32("kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz") | ||||||
|  | 	receiverAddr := testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") | ||||||
|  | 	suite.Require().NoError(err) | ||||||
|  | 
 | ||||||
|  | 	type errArgs struct { | ||||||
|  | 		expectPass bool | ||||||
|  | 		contains   string | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name                  string | ||||||
|  | 		userBankBalance       sdkmath.Int | ||||||
|  | 		expUserErc20Balance   sdkmath.Int | ||||||
|  | 		moduleErc20Balance    sdkmath.Int | ||||||
|  | 		expModuleErc20Balance sdkmath.Int | ||||||
|  | 		disablePair           bool | ||||||
|  | 		conversionDenom       string | ||||||
|  | 		coinToConvert         sdk.Coin | ||||||
|  | 		errArgs               errArgs | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:                  "success", | ||||||
|  | 			userBankBalance:       sdkmath.NewInt(1_234), | ||||||
|  | 			moduleErc20Balance:    sdkmath.NewInt(10e13), | ||||||
|  | 			expUserErc20Balance:   sdkmath.NewInt(1.234e13), | ||||||
|  | 			expModuleErc20Balance: sdkmath.NewInt(8.766e13), | ||||||
|  | 			coinToConvert:         sdk.NewCoin(denom, sdkmath.NewInt(1_234)), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:                  "success - convert smallest unit", | ||||||
|  | 			userBankBalance:       sdkmath.NewInt(2), | ||||||
|  | 			moduleErc20Balance:    sdkmath.NewInt(10e13), | ||||||
|  | 			expUserErc20Balance:   sdkmath.NewInt(1e10), | ||||||
|  | 			expModuleErc20Balance: sdkmath.NewInt(9.999e13), | ||||||
|  | 			coinToConvert:         sdk.NewCoin(denom, sdkmath.NewInt(1)), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:                  "success - no change when 0 amount converted", | ||||||
|  | 			userBankBalance:       sdkmath.NewInt(1234), | ||||||
|  | 			moduleErc20Balance:    sdkmath.NewInt(1e14), | ||||||
|  | 			expUserErc20Balance:   sdkmath.ZeroInt(), | ||||||
|  | 			expModuleErc20Balance: sdkmath.NewInt(1e14), | ||||||
|  | 			coinToConvert:         sdk.NewCoin(denom, sdkmath.NewInt(0)), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:               "error - bep3 not enabled", | ||||||
|  | 			userBankBalance:    sdkmath.NewInt(1_234), | ||||||
|  | 			moduleErc20Balance: sdkmath.NewInt(1e14), | ||||||
|  | 			disablePair:        true, | ||||||
|  | 			coinToConvert:      sdk.NewCoin(denom, sdkmath.NewInt(1_234)), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: false, | ||||||
|  | 				contains:   fmt.Sprintf("%s: ERC20 token not enabled to convert to sdk.Coin", denom), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:               "error - module account does not have enough balance to unlock", | ||||||
|  | 			userBankBalance:    sdkmath.NewInt(1_234), | ||||||
|  | 			moduleErc20Balance: sdkmath.NewInt(1e9), | ||||||
|  | 			coinToConvert:      sdk.NewCoin(denom, sdkmath.NewInt(1)), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: false, | ||||||
|  | 				contains:   "execution reverted: ERC20: transfer amount exceeds balance", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		{ | ||||||
|  | 			name:                  "success - not bep3 conversion", | ||||||
|  | 			conversionDenom:       "hard", | ||||||
|  | 			userBankBalance:       sdkmath.NewInt(1_234), | ||||||
|  | 			moduleErc20Balance:    sdkmath.NewInt(2_000), | ||||||
|  | 			expUserErc20Balance:   sdkmath.NewInt(1_234), | ||||||
|  | 			expModuleErc20Balance: sdkmath.NewInt(766), | ||||||
|  | 			coinToConvert:         sdk.NewCoin("hard", sdkmath.NewInt(1_234)), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:               "error - user converting more than user balance", | ||||||
|  | 			userBankBalance:    sdkmath.NewInt(1_234), | ||||||
|  | 			moduleErc20Balance: sdkmath.NewInt(1e14), | ||||||
|  | 			coinToConvert:      sdk.NewCoin(denom, sdkmath.NewInt(20_000)), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: false, | ||||||
|  | 				contains:   fmt.Sprintf("spendable balance 1234%s is smaller than 20000%s: insufficient funds", denom, denom), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tc := range tests { | ||||||
|  | 		suite.Run(fmt.Sprintf("%s: %s", denom, tc.name), func() { | ||||||
|  | 			suite.SetupTest() | ||||||
|  | 			contractAddr := suite.DeployERC20() | ||||||
|  | 
 | ||||||
|  | 			// override conversion denom if needed
 | ||||||
|  | 			conversionDenom := tc.conversionDenom | ||||||
|  | 			if conversionDenom == "" { | ||||||
|  | 				conversionDenom = denom | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if !tc.disablePair { | ||||||
|  | 				suite.setEnabledConversionPairDenom(conversionDenom) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			pair := types.NewConversionPair( | ||||||
|  | 				contractAddr, | ||||||
|  | 				conversionDenom, | ||||||
|  | 			) | ||||||
|  | 
 | ||||||
|  | 			// fund user & module account
 | ||||||
|  | 			if tc.userBankBalance.GT(sdkmath.ZeroInt()) { | ||||||
|  | 				err = suite.App.FundAccount( | ||||||
|  | 					suite.Ctx, | ||||||
|  | 					invoker, | ||||||
|  | 					sdk.NewCoins(sdk.NewCoin(conversionDenom, tc.userBankBalance)), | ||||||
|  | 				) | ||||||
|  | 				suite.Require().NoError(err) | ||||||
|  | 			} | ||||||
|  | 			if tc.moduleErc20Balance.GT(sdkmath.ZeroInt()) { | ||||||
|  | 				err := suite.Keeper.MintERC20( | ||||||
|  | 					suite.Ctx, | ||||||
|  | 					pair.GetAddress(), // contractAddr
 | ||||||
|  | 					types.NewInternalEVMAddress(types.ModuleEVMAddress), //receiver
 | ||||||
|  | 					tc.moduleErc20Balance.BigInt(), | ||||||
|  | 				) | ||||||
|  | 				suite.Require().NoError(err) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// execute bep3 conversion
 | ||||||
|  | 			err := suite.Keeper.ConvertCoinToERC20(suite.Ctx, invoker, receiverAddr, tc.coinToConvert) | ||||||
|  | 
 | ||||||
|  | 			if tc.errArgs.expectPass { | ||||||
|  | 				suite.Require().NoError(err) | ||||||
|  | 
 | ||||||
|  | 				// validate user balance
 | ||||||
|  | 				bal := suite.GetERC20BalanceOf( | ||||||
|  | 					types.ERC20MintableBurnableContract.ABI, | ||||||
|  | 					pair.GetAddress(), | ||||||
|  | 					receiverAddr, | ||||||
|  | 				) | ||||||
|  | 				suite.Require().Equal( | ||||||
|  | 					tc.expUserErc20Balance.BigInt().Int64(), | ||||||
|  | 					bal.Int64(), | ||||||
|  | 					"user erc20 balance should match expected amount", | ||||||
|  | 				) | ||||||
|  | 
 | ||||||
|  | 				// validate module balance
 | ||||||
|  | 				bal = suite.GetERC20BalanceOf( | ||||||
|  | 					types.ERC20MintableBurnableContract.ABI, | ||||||
|  | 					pair.GetAddress(), | ||||||
|  | 					types.NewInternalEVMAddress(types.ModuleEVMAddress), | ||||||
|  | 				) | ||||||
|  | 				suite.Require().Equal( | ||||||
|  | 					tc.expModuleErc20Balance.BigInt().Int64(), | ||||||
|  | 					bal.Int64(), | ||||||
|  | 					"module erc20 balance should match expected amount", | ||||||
|  | 				) | ||||||
|  | 
 | ||||||
|  | 				// keeper event
 | ||||||
|  | 				suite.EventsContains(suite.GetEvents(), | ||||||
|  | 					sdk.NewEvent( | ||||||
|  | 						types.EventTypeConvertCoinToERC20, | ||||||
|  | 						sdk.NewAttribute(types.AttributeKeyInitiator, invoker.String()), | ||||||
|  | 						sdk.NewAttribute(types.AttributeKeyReceiver, receiverAddr.String()), | ||||||
|  | 						sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()), | ||||||
|  | 						sdk.NewAttribute(types.AttributeKeyAmount, tc.coinToConvert.String()), | ||||||
|  | 					)) | ||||||
|  | 			} else { | ||||||
|  | 				suite.Require().Error(err) | ||||||
|  | 				suite.Require().Contains(err.Error(), tc.errArgs.contains) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *Bep3ConversionTestSuite) testConvertBep3ERC20ToCoin(denom string) { | ||||||
|  | 	invoker := testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") | ||||||
|  | 	invokerCosmosAddr, err := sdk.AccAddressFromHexUnsafe(invoker.String()[2:]) | ||||||
|  | 	suite.Require().NoError(err) | ||||||
|  | 
 | ||||||
|  | 	type errArgs struct { | ||||||
|  | 		expectPass bool | ||||||
|  | 		contains   string | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name                string | ||||||
|  | 		contractAddr        string | ||||||
|  | 		conversionDenom     string | ||||||
|  | 		disablePair         bool | ||||||
|  | 		userErc20Balance    sdkmath.Int | ||||||
|  | 		expUserBankBalance  sdkmath.Int | ||||||
|  | 		expUserErc20Balance sdkmath.Int | ||||||
|  | 		convertAmount       sdkmath.Int | ||||||
|  | 		errArgs             errArgs | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:                "success - conversion with no dust", | ||||||
|  | 			userErc20Balance:    sdkmath.NewInt(1.12e18), | ||||||
|  | 			expUserBankBalance:  sdkmath.NewInt(1.0031e8), | ||||||
|  | 			expUserErc20Balance: sdkmath.NewInt(0.1169e18), | ||||||
|  | 			convertAmount:       sdkmath.NewInt(1.0031e18), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:                "success - convert smallest bank unit", | ||||||
|  | 			userErc20Balance:    sdkmath.NewInt(2e18), | ||||||
|  | 			expUserBankBalance:  sdkmath.NewInt(1), | ||||||
|  | 			expUserErc20Balance: sdkmath.NewInt(1.99999999e18), | ||||||
|  | 			convertAmount:       sdkmath.NewInt(1.12e10), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:                "success - bnb conversion with dust", | ||||||
|  | 			userErc20Balance:    sdkmath.NewInt(2e18), | ||||||
|  | 			expUserBankBalance:  sdkmath.NewInt(12), | ||||||
|  | 			expUserErc20Balance: sdkmath.NewInt(1.99999988e18), | ||||||
|  | 			convertAmount:       sdkmath.NewInt(12.123e10), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:             "fail - converting less than 1 bank unit", | ||||||
|  | 			userErc20Balance: sdkmath.NewInt(2e18), | ||||||
|  | 			convertAmount:    sdkmath.NewInt(12e8), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: false, | ||||||
|  | 				contains:   "unable to convert bep3 coin due converting less than 1 native unit", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:             "fail - contract not enabled", | ||||||
|  | 			disablePair:      true, | ||||||
|  | 			contractAddr:     "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", | ||||||
|  | 			userErc20Balance: sdkmath.NewInt(2e18), | ||||||
|  | 			convertAmount:    sdkmath.NewInt(2e18), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: false, | ||||||
|  | 				contains:   "ERC20 token not enabled to convert", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:             "fail - converting 0 amount of bep3 erc20 token", | ||||||
|  | 			userErc20Balance: sdkmath.NewInt(2e18), | ||||||
|  | 			convertAmount:    sdkmath.NewInt(0), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: false, | ||||||
|  | 				contains:   "unable to convert bep3 coin due converting less than 1 native unit", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:                "success - not bep3 conversion", | ||||||
|  | 			conversionDenom:     "xrp", | ||||||
|  | 			userErc20Balance:    sdkmath.NewInt(2.5e18), | ||||||
|  | 			expUserBankBalance:  sdkmath.NewInt(2.1e18), | ||||||
|  | 			expUserErc20Balance: sdkmath.NewInt(0.4e18), | ||||||
|  | 			convertAmount:       sdkmath.NewInt(2.1e18), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:             "fail - user converting more than user balance", | ||||||
|  | 			userErc20Balance: sdkmath.NewInt(2e18), | ||||||
|  | 			convertAmount:    sdkmath.NewInt(2.3e18), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: false, | ||||||
|  | 				contains:   "transfer amount exceeds balance", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:                "success - user converting more than balance but only by dust amount", | ||||||
|  | 			userErc20Balance:    sdkmath.NewInt(2e18), | ||||||
|  | 			expUserBankBalance:  sdkmath.NewInt(2e8), | ||||||
|  | 			expUserErc20Balance: sdkmath.NewInt(0), | ||||||
|  | 			convertAmount:       sdkmath.NewInt(2.0000000091e18), | ||||||
|  | 			errArgs: errArgs{ | ||||||
|  | 				expectPass: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tc := range tests { | ||||||
|  | 		suite.Run(fmt.Sprintf("%s: %s", denom, tc.name), func() { | ||||||
|  | 			suite.SetupTest() | ||||||
|  | 			contractAddr := suite.DeployERC20() | ||||||
|  | 
 | ||||||
|  | 			// override conversion denom if needed
 | ||||||
|  | 			conversionDenom := tc.conversionDenom | ||||||
|  | 			if conversionDenom == "" { | ||||||
|  | 				conversionDenom = denom | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if !tc.disablePair { | ||||||
|  | 				suite.setEnabledConversionPairDenom(conversionDenom) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if tc.contractAddr != "" { | ||||||
|  | 				contractAddr = testutil.MustNewInternalEVMAddressFromString(tc.contractAddr) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			pair := types.NewConversionPair( | ||||||
|  | 				contractAddr, | ||||||
|  | 				conversionDenom, | ||||||
|  | 			) | ||||||
|  | 
 | ||||||
|  | 			// fund user erc20 balance
 | ||||||
|  | 			if tc.userErc20Balance.GT(sdkmath.ZeroInt()) { | ||||||
|  | 				err := suite.Keeper.MintERC20( | ||||||
|  | 					suite.Ctx, | ||||||
|  | 					pair.GetAddress(), | ||||||
|  | 					invoker, | ||||||
|  | 					tc.userErc20Balance.BigInt(), | ||||||
|  | 				) | ||||||
|  | 				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(conversionDenom, sdk.ZeroInt()))) | ||||||
|  | 			suite.Require().NoError(err) | ||||||
|  | 
 | ||||||
|  | 			// execute bep3 conversion
 | ||||||
|  | 			err := suite.Keeper.ConvertERC20ToCoin(suite.Ctx, invoker, invokerCosmosAddr, pair.GetAddress(), tc.convertAmount) | ||||||
|  | 
 | ||||||
|  | 			if tc.errArgs.expectPass { | ||||||
|  | 				suite.Require().NoError(err) | ||||||
|  | 
 | ||||||
|  | 				// validate user balance after conversion
 | ||||||
|  | 				bal := suite.GetERC20BalanceOf( | ||||||
|  | 					types.ERC20MintableBurnableContract.ABI, | ||||||
|  | 					pair.GetAddress(), | ||||||
|  | 					testutil.MustNewInternalEVMAddressFromString(invoker.String()), | ||||||
|  | 				) | ||||||
|  | 				suite.Require().Equal(tc.expUserErc20Balance.BigInt().Int64(), bal.Int64(), "user erc20 balance is invalid") | ||||||
|  | 
 | ||||||
|  | 				// validate user coin balance
 | ||||||
|  | 				coinBal := suite.App.GetBankKeeper().GetBalance(suite.Ctx, invokerCosmosAddr, pair.Denom) | ||||||
|  | 				suite.Require().Equal(tc.expUserBankBalance, coinBal.Amount, "user coin balance is invalid") | ||||||
|  | 
 | ||||||
|  | 				// keeper event
 | ||||||
|  | 				suite.EventsContains(suite.GetEvents(), | ||||||
|  | 					sdk.NewEvent( | ||||||
|  | 						types.EventTypeConvertERC20ToCoin, | ||||||
|  | 						sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()), | ||||||
|  | 						sdk.NewAttribute(types.AttributeKeyInitiator, invoker.String()), | ||||||
|  | 						sdk.NewAttribute(types.AttributeKeyReceiver, invokerCosmosAddr.String()), | ||||||
|  | 						sdk.NewAttribute(types.AttributeKeyAmount, sdk.NewCoin(pair.Denom, tc.expUserBankBalance).String()), | ||||||
|  | 					)) | ||||||
|  | 			} else { | ||||||
|  | 				suite.Require().Error(err) | ||||||
|  | 				suite.Require().Contains(err.Error(), tc.errArgs.contains) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -11,4 +11,5 @@ var ( | |||||||
| 	ErrUnexpectedContractEvent      = errorsmod.Register(ModuleName, 6, "unexpected contract event") | 	ErrUnexpectedContractEvent      = errorsmod.Register(ModuleName, 6, "unexpected contract event") | ||||||
| 	ErrInvalidCosmosDenom           = errorsmod.Register(ModuleName, 7, "invalid cosmos denom") | 	ErrInvalidCosmosDenom           = errorsmod.Register(ModuleName, 7, "invalid cosmos denom") | ||||||
| 	ErrSDKConversionNotEnabled      = errorsmod.Register(ModuleName, 8, "sdk.Coin not enabled to convert to ERC20 token") | 	ErrSDKConversionNotEnabled      = errorsmod.Register(ModuleName, 8, "sdk.Coin not enabled to convert to ERC20 token") | ||||||
|  | 	ErrInsufficientConversionAmount = errorsmod.Register(ModuleName, 9, "insufficient conversion amount") | ||||||
| ) | ) | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	![37929162+mergify[bot]@users.noreply.github.com](/assets/img/avatar_default.png) mergify[bot]
						mergify[bot]