mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 03:57:44 +00:00 
			
		
		
		
	feat(evmutil): add CosmosCoinsFullyBackedInvariant (#1610)
* add IterateAllDeployedCosmosCoinContracts method * refactor unpacking big int from erc20 query * add QueryERC20TotalSupply method * feat(evmutil): add CosmosCoinsFullyBackedInvariant * update changelog
This commit is contained in:
		
							parent
							
								
									f4b8bf8f07
								
							
						
					
					
						commit
						528be6350e
					
				@ -44,6 +44,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
 | 
				
			|||||||
- (evmutil) [#1604] Emit events for MsgConvertCosmosCoinToERC20: `message` & `convert_cosmos_coin_to_erc20`
 | 
					- (evmutil) [#1604] Emit events for MsgConvertCosmosCoinToERC20: `message` & `convert_cosmos_coin_to_erc20`
 | 
				
			||||||
- (evmutil) [#1605] Add query for deployed ERC20 contracts representing Cosmos coins in the EVM
 | 
					- (evmutil) [#1605] Add query for deployed ERC20 contracts representing Cosmos coins in the EVM
 | 
				
			||||||
- (evmutil) [#1609] Add MsgConvertCosmosCoinFromERC20 for converting the ERC20 back to an sdk.Coin
 | 
					- (evmutil) [#1609] Add MsgConvertCosmosCoinFromERC20 for converting the ERC20 back to an sdk.Coin
 | 
				
			||||||
 | 
					- (evmutil) [#1610] Add new invariant checking that ERC20s are fully backed by sdk.Coins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Client Breaking
 | 
					### Client Breaking
 | 
				
			||||||
- (evmutil) [#1603] Renamed error `ErrConversionNotEnabled` to `ErrEVMConversionNotEnabled`
 | 
					- (evmutil) [#1603] Renamed error `ErrConversionNotEnabled` to `ErrEVMConversionNotEnabled`
 | 
				
			||||||
@ -252,6 +253,7 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md).
 | 
				
			|||||||
- [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run
 | 
					- [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run
 | 
				
			||||||
  large-scale simulations remotely using aws-batch
 | 
					  large-scale simulations remotely using aws-batch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[#1610]: https://github.com/Kava-Labs/kava/pull/1610
 | 
				
			||||||
[#1609]: https://github.com/Kava-Labs/kava/pull/1609
 | 
					[#1609]: https://github.com/Kava-Labs/kava/pull/1609
 | 
				
			||||||
[#1605]: https://github.com/Kava-Labs/kava/pull/1605
 | 
					[#1605]: https://github.com/Kava-Labs/kava/pull/1605
 | 
				
			||||||
[#1604]: https://github.com/Kava-Labs/kava/pull/1604
 | 
					[#1604]: https://github.com/Kava-Labs/kava/pull/1604
 | 
				
			||||||
 | 
				
			|||||||
@ -1138,6 +1138,7 @@ func (app *App) loadBlockedMaccAddrs() map[string]bool {
 | 
				
			|||||||
		app.accountKeeper.GetModuleAddress(kavadisttypes.FundModuleAccount).String(): true,
 | 
							app.accountKeeper.GetModuleAddress(kavadisttypes.FundModuleAccount).String(): true,
 | 
				
			||||||
		// community
 | 
							// community
 | 
				
			||||||
		app.accountKeeper.GetModuleAddress(communitytypes.ModuleAccountName).String(): true,
 | 
							app.accountKeeper.GetModuleAddress(communitytypes.ModuleAccountName).String(): true,
 | 
				
			||||||
 | 
							// NOTE: if adding evmutil, adjust the cosmos-coins-fully-backed-invariant accordingly.
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for addr := range modAccAddrs {
 | 
						for addr := range modAccAddrs {
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,10 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	erc20BalanceOfMethod = "balanceOf"
 | 
						erc20BalanceOfMethod   = "balanceOf"
 | 
				
			||||||
 | 
						erc20BurnMethod        = "burn"
 | 
				
			||||||
 | 
						erc20MintMethod        = "mint"
 | 
				
			||||||
 | 
						erc20TotalSupplyMethod = "totalSupply"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeployTestMintableERC20Contract deploys an ERC20 contract on the EVM as the
 | 
					// DeployTestMintableERC20Contract deploys an ERC20 contract on the EVM as the
 | 
				
			||||||
@ -149,7 +152,7 @@ func (k Keeper) MintERC20(
 | 
				
			|||||||
		types.ERC20MintableBurnableContract.ABI,
 | 
							types.ERC20MintableBurnableContract.ABI,
 | 
				
			||||||
		types.ModuleEVMAddress,
 | 
							types.ModuleEVMAddress,
 | 
				
			||||||
		contractAddr,
 | 
							contractAddr,
 | 
				
			||||||
		"mint",
 | 
							erc20MintMethod,
 | 
				
			||||||
		// Mint ERC20 args
 | 
							// Mint ERC20 args
 | 
				
			||||||
		receiver.Address,
 | 
							receiver.Address,
 | 
				
			||||||
		amount,
 | 
							amount,
 | 
				
			||||||
@ -170,7 +173,7 @@ func (k Keeper) BurnERC20(
 | 
				
			|||||||
		types.ERC20KavaWrappedCosmosCoinContract.ABI,
 | 
							types.ERC20KavaWrappedCosmosCoinContract.ABI,
 | 
				
			||||||
		types.ModuleEVMAddress,
 | 
							types.ModuleEVMAddress,
 | 
				
			||||||
		contractAddr,
 | 
							contractAddr,
 | 
				
			||||||
		"burn",
 | 
							erc20BurnMethod,
 | 
				
			||||||
		// Burn ERC20 args
 | 
							// Burn ERC20 args
 | 
				
			||||||
		initiator.Address,
 | 
							initiator.Address,
 | 
				
			||||||
		amount,
 | 
							amount,
 | 
				
			||||||
@ -179,6 +182,8 @@ func (k Keeper) BurnERC20(
 | 
				
			|||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// QueryERC20BalanceOf makes a contract call to the balanceOf method of the ERC20 contract to get
 | 
				
			||||||
 | 
					// the ERC20 balance of the given account.
 | 
				
			||||||
func (k Keeper) QueryERC20BalanceOf(
 | 
					func (k Keeper) QueryERC20BalanceOf(
 | 
				
			||||||
	ctx sdk.Context,
 | 
						ctx sdk.Context,
 | 
				
			||||||
	contractAddr types.InternalEVMAddress,
 | 
						contractAddr types.InternalEVMAddress,
 | 
				
			||||||
@ -197,6 +202,31 @@ func (k Keeper) QueryERC20BalanceOf(
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return unpackERC20ResToBigInt(res, erc20BalanceOfMethod)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// QueryERC20TotalSupply makes a contract call to the totalSupply method of the ERC20 contract to
 | 
				
			||||||
 | 
					// get the total supply of the token.
 | 
				
			||||||
 | 
					func (k Keeper) QueryERC20TotalSupply(
 | 
				
			||||||
 | 
						ctx sdk.Context,
 | 
				
			||||||
 | 
						contractAddr types.InternalEVMAddress,
 | 
				
			||||||
 | 
					) (*big.Int, error) {
 | 
				
			||||||
 | 
						res, err := k.CallEVM(
 | 
				
			||||||
 | 
							ctx,
 | 
				
			||||||
 | 
							types.ERC20KavaWrappedCosmosCoinContract.ABI,
 | 
				
			||||||
 | 
							types.ModuleEVMAddress,
 | 
				
			||||||
 | 
							contractAddr,
 | 
				
			||||||
 | 
							erc20TotalSupplyMethod,
 | 
				
			||||||
 | 
							// totalSupply takes no args
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return unpackERC20ResToBigInt(res, erc20TotalSupplyMethod)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func unpackERC20ResToBigInt(res *evmtypes.MsgEthereumTxResponse, methodName string) (*big.Int, error) {
 | 
				
			||||||
	if res.Failed() {
 | 
						if res.Failed() {
 | 
				
			||||||
		if res.VmError == vm.ErrExecutionReverted.Error() {
 | 
							if res.VmError == vm.ErrExecutionReverted.Error() {
 | 
				
			||||||
			// Unpacks revert
 | 
								// Unpacks revert
 | 
				
			||||||
@ -206,11 +236,11 @@ func (k Keeper) QueryERC20BalanceOf(
 | 
				
			|||||||
		return nil, status.Error(codes.Internal, res.VmError)
 | 
							return nil, status.Error(codes.Internal, res.VmError)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	anyOutput, err := types.ERC20MintableBurnableContract.ABI.Unpack(erc20BalanceOfMethod, res.Ret)
 | 
						anyOutput, err := types.ERC20MintableBurnableContract.ABI.Unpack(methodName, res.Ret)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf(
 | 
							return nil, fmt.Errorf(
 | 
				
			||||||
			"failed to unpack method %v response: %w",
 | 
								"failed to unpack method %v response: %w",
 | 
				
			||||||
			erc20BalanceOfMethod,
 | 
								methodName,
 | 
				
			||||||
			err,
 | 
								err,
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -218,7 +248,7 @@ func (k Keeper) QueryERC20BalanceOf(
 | 
				
			|||||||
	if len(anyOutput) != 1 {
 | 
						if len(anyOutput) != 1 {
 | 
				
			||||||
		return nil, fmt.Errorf(
 | 
							return nil, fmt.Errorf(
 | 
				
			||||||
			"invalid ERC20 %v call return outputs %v, expected %v",
 | 
								"invalid ERC20 %v call return outputs %v, expected %v",
 | 
				
			||||||
			erc20BalanceOfMethod,
 | 
								methodName,
 | 
				
			||||||
			len(anyOutput),
 | 
								len(anyOutput),
 | 
				
			||||||
			1,
 | 
								1,
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,27 @@ func (suite *ERC20TestSuite) TestERC20Mint() {
 | 
				
			|||||||
	suite.Require().Equal(big.NewInt(1234), balance)
 | 
						suite.Require().Equal(big.NewInt(1234), balance)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *ERC20TestSuite) TestQueryERC20TotalSupply() {
 | 
				
			||||||
 | 
						suite.Run("with no balance", func() {
 | 
				
			||||||
 | 
							bal, err := suite.Keeper.QueryERC20TotalSupply(suite.Ctx, suite.contractAddr)
 | 
				
			||||||
 | 
							suite.NoError(err)
 | 
				
			||||||
 | 
							suite.BigIntsEqual(big.NewInt(0), bal, "expected total supply of 0")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("with balance", func() {
 | 
				
			||||||
 | 
							amount := big.NewInt(1e10)
 | 
				
			||||||
 | 
							expectedTotal := big.NewInt(3e10)
 | 
				
			||||||
 | 
							// mint 1e10 to three random accounts
 | 
				
			||||||
 | 
							suite.NoError(suite.Keeper.MintERC20(suite.Ctx, suite.contractAddr, testutil.RandomInternalEVMAddress(), amount))
 | 
				
			||||||
 | 
							suite.NoError(suite.Keeper.MintERC20(suite.Ctx, suite.contractAddr, testutil.RandomInternalEVMAddress(), amount))
 | 
				
			||||||
 | 
							suite.NoError(suite.Keeper.MintERC20(suite.Ctx, suite.contractAddr, testutil.RandomInternalEVMAddress(), amount))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bal, err := suite.Keeper.QueryERC20TotalSupply(suite.Ctx, suite.contractAddr)
 | 
				
			||||||
 | 
							suite.NoError(err)
 | 
				
			||||||
 | 
							suite.BigIntsEqual(expectedTotal, bal, "unexpected total supply after minting")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *ERC20TestSuite) TestDeployKavaWrappedCosmosCoinERC20Contract() {
 | 
					func (suite *ERC20TestSuite) TestDeployKavaWrappedCosmosCoinERC20Contract() {
 | 
				
			||||||
	suite.Run("fails to deploy invalid contract", func() {
 | 
						suite.Run("fails to deploy invalid contract", func() {
 | 
				
			||||||
		// empty other fields means this token is invalid.
 | 
							// empty other fields means this token is invalid.
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
package keeper
 | 
					package keeper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
 | 
						authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -11,6 +13,7 @@ import (
 | 
				
			|||||||
func RegisterInvariants(ir sdk.InvariantRegistry, bankK types.BankKeeper, k Keeper) {
 | 
					func RegisterInvariants(ir sdk.InvariantRegistry, bankK types.BankKeeper, k Keeper) {
 | 
				
			||||||
	ir.RegisterRoute(types.ModuleName, "fully-backed", FullyBackedInvariant(bankK, k))
 | 
						ir.RegisterRoute(types.ModuleName, "fully-backed", FullyBackedInvariant(bankK, k))
 | 
				
			||||||
	ir.RegisterRoute(types.ModuleName, "small-balances", SmallBalancesInvariant(bankK, k))
 | 
						ir.RegisterRoute(types.ModuleName, "small-balances", SmallBalancesInvariant(bankK, k))
 | 
				
			||||||
 | 
						ir.RegisterRoute(types.ModuleName, "cosmos-coins-fully-backed", CosmosCoinsFullyBackedInvariant(bankK, k))
 | 
				
			||||||
	// Disable this invariant due to some issues with it requiring some staking params to be set in genesis.
 | 
						// Disable this invariant due to some issues with it requiring some staking params to be set in genesis.
 | 
				
			||||||
	// ir.RegisterRoute(types.ModuleName, "backed-conversion-coins", BackedCoinsInvariant(bankK, k))
 | 
						// ir.RegisterRoute(types.ModuleName, "backed-conversion-coins", BackedCoinsInvariant(bankK, k))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -24,6 +27,9 @@ func AllInvariants(bankK types.BankKeeper, k Keeper) sdk.Invariant {
 | 
				
			|||||||
		if res, stop := BackedCoinsInvariant(bankK, k)(ctx); stop {
 | 
							if res, stop := BackedCoinsInvariant(bankK, k)(ctx); stop {
 | 
				
			||||||
			return res, stop
 | 
								return res, stop
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if res, stop := CosmosCoinsFullyBackedInvariant(bankK, k)(ctx); stop {
 | 
				
			||||||
 | 
								return res, stop
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		return SmallBalancesInvariant(bankK, k)(ctx)
 | 
							return SmallBalancesInvariant(bankK, k)(ctx)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -105,3 +111,38 @@ func BackedCoinsInvariant(_ types.BankKeeper, k Keeper) sdk.Invariant {
 | 
				
			|||||||
		return message, broken
 | 
							return message, broken
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CosmosCoinsFullyBackedInvariant ensures the total supply of ERC20 representations of sdk.Coins
 | 
				
			||||||
 | 
					// match the balances in the module account.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This invariant depends on the fact that coins can only become part of the balance through
 | 
				
			||||||
 | 
					// conversion to ERC20s.
 | 
				
			||||||
 | 
					// If in the future sdk.Coins can be sent directly to the module account,
 | 
				
			||||||
 | 
					// or the module account balance can be increased in any other way,
 | 
				
			||||||
 | 
					// this invariant should be changed from checking that the balance equals the total supply,
 | 
				
			||||||
 | 
					// to check that the balance is greater than or equal to the total supply.
 | 
				
			||||||
 | 
					func CosmosCoinsFullyBackedInvariant(bankK types.BankKeeper, k Keeper) sdk.Invariant {
 | 
				
			||||||
 | 
						broken := false
 | 
				
			||||||
 | 
						message := sdk.FormatInvariant(
 | 
				
			||||||
 | 
							types.ModuleName,
 | 
				
			||||||
 | 
							"cosmos coins fully-backed broken",
 | 
				
			||||||
 | 
							"ERC20 total supply is not equal to module account balance",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						maccAddress := authtypes.NewModuleAddress(types.ModuleName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return func(ctx sdk.Context) (string, bool) {
 | 
				
			||||||
 | 
							k.IterateAllDeployedCosmosCoinContracts(ctx, func(c types.DeployedCosmosCoinContract) bool {
 | 
				
			||||||
 | 
								moduleBalance := bankK.GetBalance(ctx, maccAddress, c.CosmosDenom).Amount
 | 
				
			||||||
 | 
								totalSupply, err := k.QueryERC20TotalSupply(ctx, *c.Address)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									panic(fmt.Sprintf("failed to query total supply for %+v", c))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// expect total supply to equal balance in the module
 | 
				
			||||||
 | 
								if totalSupply.Cmp(moduleBalance.BigInt()) != 0 {
 | 
				
			||||||
 | 
									broken = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return broken
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return message, broken
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -6,8 +6,12 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	sdkmath "cosmossdk.io/math"
 | 
						sdkmath "cosmossdk.io/math"
 | 
				
			||||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
 | 
						authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
 | 
				
			||||||
 | 
						bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
 | 
				
			||||||
 | 
						banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
 | 
				
			||||||
	"github.com/stretchr/testify/suite"
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/app"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/evmutil/keeper"
 | 
						"github.com/kava-labs/kava/x/evmutil/keeper"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/evmutil/testutil"
 | 
						"github.com/kava-labs/kava/x/evmutil/testutil"
 | 
				
			||||||
	"github.com/kava-labs/kava/x/evmutil/types"
 | 
						"github.com/kava-labs/kava/x/evmutil/types"
 | 
				
			||||||
@ -17,6 +21,9 @@ type invariantTestSuite struct {
 | 
				
			|||||||
	testutil.Suite
 | 
						testutil.Suite
 | 
				
			||||||
	invariants   map[string]map[string]sdk.Invariant
 | 
						invariants   map[string]map[string]sdk.Invariant
 | 
				
			||||||
	contractAddr types.InternalEVMAddress
 | 
						contractAddr types.InternalEVMAddress
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cosmosCoin             types.AllowedCosmosCoinERC20Token
 | 
				
			||||||
 | 
						cosmosCoinContractAddr types.InternalEVMAddress
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestInvariantTestSuite(t *testing.T) {
 | 
					func TestInvariantTestSuite(t *testing.T) {
 | 
				
			||||||
@ -62,6 +69,31 @@ func (suite *invariantTestSuite) SetupValidState() {
 | 
				
			|||||||
		big.NewInt(1000),
 | 
							big.NewInt(1000),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	suite.Require().NoError(err)
 | 
						suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// setup a cosmos coin erc20 with supply
 | 
				
			||||||
 | 
						tokenInfo := types.NewAllowedCosmosCoinERC20Token("magic", "Magic coin", "MAGIC", 6)
 | 
				
			||||||
 | 
						suite.cosmosCoin = tokenInfo
 | 
				
			||||||
 | 
						params := suite.Keeper.GetParams(suite.Ctx)
 | 
				
			||||||
 | 
						params.AllowedCosmosDenoms = append(params.AllowedCosmosDenoms, tokenInfo)
 | 
				
			||||||
 | 
						suite.Keeper.SetParams(suite.Ctx, params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.cosmosCoinContractAddr, err = suite.Keeper.GetOrDeployCosmosCoinERC20Contract(suite.Ctx, tokenInfo)
 | 
				
			||||||
 | 
						suite.NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// setup converted coin position
 | 
				
			||||||
 | 
						err = suite.Keeper.MintERC20(
 | 
				
			||||||
 | 
							suite.Ctx,
 | 
				
			||||||
 | 
							suite.cosmosCoinContractAddr,
 | 
				
			||||||
 | 
							testutil.RandomInternalEVMAddress(),
 | 
				
			||||||
 | 
							big.NewInt(1e12),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						suite.NoError(err)
 | 
				
			||||||
 | 
						err = suite.App.FundModuleAccount(
 | 
				
			||||||
 | 
							suite.Ctx,
 | 
				
			||||||
 | 
							types.ModuleName,
 | 
				
			||||||
 | 
							sdk.NewCoins(sdk.NewInt64Coin(tokenInfo.CosmosDenom, 1e12)),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						suite.NoError(err)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RegisterRoutes implements sdk.InvariantRegistry
 | 
					// RegisterRoutes implements sdk.InvariantRegistry
 | 
				
			||||||
@ -137,3 +169,49 @@ func (suite *invariantTestSuite) TestSmallBalances() {
 | 
				
			|||||||
	suite.Equal("evmutil: small balances broken invariant\nminor balances not all less than overflow\n", message)
 | 
						suite.Equal("evmutil: small balances broken invariant\nminor balances not all less than overflow\n", message)
 | 
				
			||||||
	suite.Equal(true, broken)
 | 
						suite.Equal(true, broken)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// the cosmos-coins-fully-backed invariant depends on 1-to-1 mapping of module balance to erc20s
 | 
				
			||||||
 | 
					// if coins can be sent directly to the module account, this assumption is broken.
 | 
				
			||||||
 | 
					// this test verifies that coins cannot be directly sent to the module account.
 | 
				
			||||||
 | 
					func (suite *invariantTestSuite) TestSendToModuleAccountNotAllowed() {
 | 
				
			||||||
 | 
						bKeeper := suite.App.GetBankKeeper()
 | 
				
			||||||
 | 
						maccAddress := authtypes.NewModuleAddress(types.ModuleName)
 | 
				
			||||||
 | 
						suite.True(bKeeper.BlockedAddr(maccAddress))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						coins := sdk.NewCoins(sdk.NewInt64Coin(suite.cosmosCoin.CosmosDenom, 1e7))
 | 
				
			||||||
 | 
						addr := app.RandomAddress()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := suite.App.FundAccount(suite.Ctx, addr, coins)
 | 
				
			||||||
 | 
						suite.NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bankMsgServer := bankkeeper.NewMsgServerImpl(bKeeper)
 | 
				
			||||||
 | 
						_, err = bankMsgServer.Send(suite.Ctx, &banktypes.MsgSend{
 | 
				
			||||||
 | 
							FromAddress: addr.String(),
 | 
				
			||||||
 | 
							ToAddress:   maccAddress.String(),
 | 
				
			||||||
 | 
							Amount:      coins,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						suite.ErrorContains(err, "kava1w9vxuke5dz6hyza2j932qgmxltnfxwl78u920k is not allowed to receive funds: unauthorized")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *invariantTestSuite) TestCosmosCoinsFullyBackedInvariant() {
 | 
				
			||||||
 | 
						invariantName := "cosmos-coins-fully-backed"
 | 
				
			||||||
 | 
						// default state is valid
 | 
				
			||||||
 | 
						_, broken := suite.runInvariant(invariantName, keeper.CosmosCoinsFullyBackedInvariant)
 | 
				
			||||||
 | 
						suite.False(broken)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.SetupValidState()
 | 
				
			||||||
 | 
						_, broken = suite.runInvariant(invariantName, keeper.CosmosCoinsFullyBackedInvariant)
 | 
				
			||||||
 | 
						suite.False(broken)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// break the invariant by removing module account balance without adjusting token supply
 | 
				
			||||||
 | 
						err := suite.BankKeeper.SendCoinsFromModuleToAccount(
 | 
				
			||||||
 | 
							suite.Ctx,
 | 
				
			||||||
 | 
							types.ModuleName,
 | 
				
			||||||
 | 
							app.RandomAddress(),
 | 
				
			||||||
 | 
							sdk.NewCoins(sdk.NewInt64Coin(suite.cosmosCoin.CosmosDenom, 1e5)),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						suite.NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, broken = suite.runInvariant(invariantName, keeper.CosmosCoinsFullyBackedInvariant)
 | 
				
			||||||
 | 
						suite.True(broken)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -212,3 +212,21 @@ func (k *Keeper) GetDeployedCosmosCoinContract(ctx sdk.Context, cosmosDenom stri
 | 
				
			|||||||
	found := len(bz) != 0
 | 
						found := len(bz) != 0
 | 
				
			||||||
	return types.BytesToInternalEVMAddress(bz), found
 | 
						return types.BytesToInternalEVMAddress(bz), found
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IterateAllDeployedCosmosCoinContracts iterates through all the deployed ERC20 contracts representing
 | 
				
			||||||
 | 
					// cosmos-sdk coins. If true is returned from the callback, iteration is halted.
 | 
				
			||||||
 | 
					func (k Keeper) IterateAllDeployedCosmosCoinContracts(ctx sdk.Context, cb func(types.DeployedCosmosCoinContract) bool) {
 | 
				
			||||||
 | 
						store := ctx.KVStore(k.storeKey)
 | 
				
			||||||
 | 
						iterator := sdk.KVStorePrefixIterator(store, types.DeployedCosmosCoinContractKeyPrefix)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer iterator.Close()
 | 
				
			||||||
 | 
						for ; iterator.Valid(); iterator.Next() {
 | 
				
			||||||
 | 
							contract := types.NewDeployedCosmosCoinContract(
 | 
				
			||||||
 | 
								types.DenomFromDeployedCosmosCoinContractKey(iterator.Key()),
 | 
				
			||||||
 | 
								types.BytesToInternalEVMAddress(iterator.Value()),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							if cb(contract) {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -391,6 +391,49 @@ func (suite *keeperTestSuite) TestDeployedCosmosCoinContractStoreState() {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *keeperTestSuite) TestIterateAllDeployedCosmosCoinContracts() {
 | 
				
			||||||
 | 
						suite.SetupTest()
 | 
				
			||||||
 | 
						address := testutil.RandomInternalEVMAddress()
 | 
				
			||||||
 | 
						denoms := []string{}
 | 
				
			||||||
 | 
						register := func(denom string) {
 | 
				
			||||||
 | 
							addr := testutil.RandomInternalEVMAddress()
 | 
				
			||||||
 | 
							if denom == "waldo" {
 | 
				
			||||||
 | 
								addr = address
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err := suite.Keeper.SetDeployedCosmosCoinContract(suite.Ctx, denom, addr)
 | 
				
			||||||
 | 
							suite.NoError(err)
 | 
				
			||||||
 | 
							denoms = append(denoms, denom)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// register some contracts
 | 
				
			||||||
 | 
						register("magic")
 | 
				
			||||||
 | 
						register("popcorn")
 | 
				
			||||||
 | 
						register("waldo")
 | 
				
			||||||
 | 
						register("zzz")
 | 
				
			||||||
 | 
						register("ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("stops when told", func() {
 | 
				
			||||||
 | 
							// test out stopping the iteration
 | 
				
			||||||
 | 
							// NOTE: don't actually look for a single contract this way. the keys are deterministic by denom.
 | 
				
			||||||
 | 
							var contract types.DeployedCosmosCoinContract
 | 
				
			||||||
 | 
							suite.Keeper.IterateAllDeployedCosmosCoinContracts(suite.Ctx, func(c types.DeployedCosmosCoinContract) bool {
 | 
				
			||||||
 | 
								contract = c
 | 
				
			||||||
 | 
								return c.CosmosDenom == "waldo"
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							suite.Equal(types.NewDeployedCosmosCoinContract("waldo", address), contract)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						suite.Run("iterates all contracts", func() {
 | 
				
			||||||
 | 
							foundDenoms := make([]string, 0, len(denoms))
 | 
				
			||||||
 | 
							suite.Keeper.IterateAllDeployedCosmosCoinContracts(suite.Ctx, func(c types.DeployedCosmosCoinContract) bool {
 | 
				
			||||||
 | 
								foundDenoms = append(foundDenoms, c.CosmosDenom)
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							suite.Len(foundDenoms, len(denoms))
 | 
				
			||||||
 | 
							suite.ElementsMatch(denoms, foundDenoms)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestKeeperTestSuite(t *testing.T) {
 | 
					func TestKeeperTestSuite(t *testing.T) {
 | 
				
			||||||
	suite.Run(t, new(keeperTestSuite))
 | 
						suite.Run(t, new(keeperTestSuite))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user