mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 23:35:19 +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 {
|
||||||
|
@ -18,6 +18,9 @@ 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