mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 15:55:18 +00:00
feat: Add upgrade handler, fractional balances & reserve transfer (#1966)
Add upgrade handler Migrates from x/evmutil to x/precisebank: - Fractional balances - Reserve funds - Mints or burns coins to ensure fractional balances are fully backed. Initialize remainder if necessary to ensure valid state. E2E test with fixed kvtool
This commit is contained in:
parent
65d091d458
commit
493ce0516f
2
Makefile
2
Makefile
@ -348,7 +348,7 @@ start-remote-sims:
|
|||||||
|
|
||||||
update-kvtool:
|
update-kvtool:
|
||||||
git submodule init || true
|
git submodule init || true
|
||||||
git submodule update
|
git submodule update --remote
|
||||||
cd tests/e2e/kvtool && make install
|
cd tests/e2e/kvtool && make install
|
||||||
|
|
||||||
.PHONY: all build-linux install build test test-cli test-all test-rest test-basic test-fuzz start-remote-sims
|
.PHONY: all build-linux install build test test-cli test-all test-rest test-basic test-fuzz start-remote-sims
|
||||||
|
273
app/upgrades.go
273
app/upgrades.go
@ -1,3 +1,274 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
func (app App) RegisterUpgradeHandlers() {}
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
|
||||||
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
|
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
||||||
|
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
|
||||||
|
evmutilkeeper "github.com/kava-labs/kava/x/evmutil/keeper"
|
||||||
|
evmutiltypes "github.com/kava-labs/kava/x/evmutil/types"
|
||||||
|
precisebankkeeper "github.com/kava-labs/kava/x/precisebank/keeper"
|
||||||
|
precisebanktypes "github.com/kava-labs/kava/x/precisebank/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UpgradeName_Mainnet = "v0.27.0"
|
||||||
|
UpgradeName_Testnet = "v0.27.0-alpha.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterUpgradeHandlers registers the upgrade handlers for the app.
|
||||||
|
func (app App) RegisterUpgradeHandlers() {
|
||||||
|
app.upgradeKeeper.SetUpgradeHandler(
|
||||||
|
UpgradeName_Mainnet,
|
||||||
|
upgradeHandler(app, UpgradeName_Mainnet),
|
||||||
|
)
|
||||||
|
app.upgradeKeeper.SetUpgradeHandler(
|
||||||
|
UpgradeName_Testnet,
|
||||||
|
upgradeHandler(app, UpgradeName_Testnet),
|
||||||
|
)
|
||||||
|
|
||||||
|
upgradeInfo, err := app.upgradeKeeper.ReadUpgradeInfoFromDisk()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
doUpgrade := upgradeInfo.Name == UpgradeName_Mainnet ||
|
||||||
|
upgradeInfo.Name == UpgradeName_Testnet
|
||||||
|
|
||||||
|
if doUpgrade && !app.upgradeKeeper.IsSkipHeight(upgradeInfo.Height) {
|
||||||
|
storeUpgrades := storetypes.StoreUpgrades{
|
||||||
|
Added: []string{
|
||||||
|
precisebanktypes.ModuleName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure store loader that checks if version == upgradeHeight and applies store upgrades
|
||||||
|
app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// upgradeHandler returns an UpgradeHandler for the given upgrade parameters.
|
||||||
|
func upgradeHandler(
|
||||||
|
app App,
|
||||||
|
name string,
|
||||||
|
) upgradetypes.UpgradeHandler {
|
||||||
|
return func(
|
||||||
|
ctx sdk.Context,
|
||||||
|
plan upgradetypes.Plan,
|
||||||
|
fromVM module.VersionMap,
|
||||||
|
) (module.VersionMap, error) {
|
||||||
|
logger := app.Logger()
|
||||||
|
logger.Info(fmt.Sprintf("running %s upgrade handler", name))
|
||||||
|
|
||||||
|
// Run migrations for all modules and return new consensus version map.
|
||||||
|
versionMap, err := app.mm.RunMigrations(ctx, app.configurator, fromVM)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("completed store migrations")
|
||||||
|
|
||||||
|
// Migration of fractional balances from x/evmutil to x/precisebank
|
||||||
|
if err := MigrateEvmutilToPrecisebank(
|
||||||
|
ctx,
|
||||||
|
app.accountKeeper,
|
||||||
|
app.bankKeeper,
|
||||||
|
app.evmutilKeeper,
|
||||||
|
app.precisebankKeeper,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("completed x/evmutil to x/precisebank migration")
|
||||||
|
|
||||||
|
return versionMap, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MigrateEvmutilToPrecisebank migrates all required state from x/evmutil to
|
||||||
|
// x/precisebank and ensures the resulting state is correct.
|
||||||
|
// This migrates the following state:
|
||||||
|
// - Fractional balances
|
||||||
|
// - Fractional balance reserve
|
||||||
|
// Initializes the following state in x/precisebank:
|
||||||
|
// - Remainder amount
|
||||||
|
func MigrateEvmutilToPrecisebank(
|
||||||
|
ctx sdk.Context,
|
||||||
|
accountKeeper evmutiltypes.AccountKeeper,
|
||||||
|
bankKeeper bankkeeper.Keeper,
|
||||||
|
evmutilKeeper evmutilkeeper.Keeper,
|
||||||
|
precisebankKeeper precisebankkeeper.Keeper,
|
||||||
|
) error {
|
||||||
|
logger := ctx.Logger()
|
||||||
|
|
||||||
|
aggregateSum, err := TransferFractionalBalances(
|
||||||
|
ctx,
|
||||||
|
evmutilKeeper,
|
||||||
|
precisebankKeeper,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fractional balances transfer: %w", err)
|
||||||
|
}
|
||||||
|
logger.Info(
|
||||||
|
"fractional balances transferred from x/evmutil to x/precisebank",
|
||||||
|
"aggregate sum", aggregateSum,
|
||||||
|
)
|
||||||
|
|
||||||
|
remainder := InitializeRemainder(ctx, precisebankKeeper, aggregateSum)
|
||||||
|
logger.Info("remainder amount initialized in x/precisebank", "remainder", remainder)
|
||||||
|
|
||||||
|
// Migrate fractional balances, reserve, and ensure reserve fully backs all
|
||||||
|
// fractional balances.
|
||||||
|
if err := TransferFractionalBalanceReserve(
|
||||||
|
ctx,
|
||||||
|
accountKeeper,
|
||||||
|
bankKeeper,
|
||||||
|
precisebankKeeper,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("reserve transfer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferFractionalBalances migrates fractional balances from x/evmutil to
|
||||||
|
// x/precisebank. It sets the fractional balance in x/precisebank and deletes
|
||||||
|
// the account from x/evmutil. Returns the aggregate sum of all fractional
|
||||||
|
// balances.
|
||||||
|
func TransferFractionalBalances(
|
||||||
|
ctx sdk.Context,
|
||||||
|
evmutilKeeper evmutilkeeper.Keeper,
|
||||||
|
precisebankKeeper precisebankkeeper.Keeper,
|
||||||
|
) (sdkmath.Int, error) {
|
||||||
|
aggregateSum := sdkmath.ZeroInt()
|
||||||
|
|
||||||
|
var iterErr error
|
||||||
|
|
||||||
|
evmutilKeeper.IterateAllAccounts(ctx, func(acc evmutiltypes.Account) bool {
|
||||||
|
// Set account balance in x/precisebank
|
||||||
|
precisebankKeeper.SetFractionalBalance(ctx, acc.Address, acc.Balance)
|
||||||
|
|
||||||
|
// Delete account from x/evmutil
|
||||||
|
iterErr := evmutilKeeper.SetAccount(ctx, evmutiltypes.Account{
|
||||||
|
Address: acc.Address,
|
||||||
|
// Set balance to 0 to delete it
|
||||||
|
Balance: sdkmath.ZeroInt(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Halt iteration if there was an error
|
||||||
|
if iterErr != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate sum of all fractional balances
|
||||||
|
aggregateSum = aggregateSum.Add(acc.Balance)
|
||||||
|
|
||||||
|
// Continue iterating
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return aggregateSum, iterErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializeRemainder initializes the remainder amount in x/precisebank. It
|
||||||
|
// calculates the remainder amount that is needed to ensure that the sum of all
|
||||||
|
// fractional balances is a multiple of the conversion factor. The remainder
|
||||||
|
// amount is stored in the store and returned.
|
||||||
|
func InitializeRemainder(
|
||||||
|
ctx sdk.Context,
|
||||||
|
precisebankKeeper precisebankkeeper.Keeper,
|
||||||
|
aggregateSum sdkmath.Int,
|
||||||
|
) sdkmath.Int {
|
||||||
|
// Extra fractional coins that exceed the conversion factor.
|
||||||
|
// This extra + remainder should equal the conversion factor to ensure
|
||||||
|
// (sum(fBalances) + remainder) % conversionFactor = 0
|
||||||
|
extraFractionalAmount := aggregateSum.Mod(precisebanktypes.ConversionFactor())
|
||||||
|
remainder := precisebanktypes.ConversionFactor().
|
||||||
|
Sub(extraFractionalAmount).
|
||||||
|
// Mod conversion factor to ensure remainder is valid.
|
||||||
|
// If extraFractionalAmount is a multiple of conversion factor, the
|
||||||
|
// remainder is 0.
|
||||||
|
Mod(precisebanktypes.ConversionFactor())
|
||||||
|
|
||||||
|
// Panics if the remainder is invalid. In a correct chain state and only
|
||||||
|
// mint/burns due to transfers, this would be 0.
|
||||||
|
precisebankKeeper.SetRemainderAmount(ctx, remainder)
|
||||||
|
|
||||||
|
return remainder
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferFractionalBalanceReserve migrates the fractional balance reserve from
|
||||||
|
// x/evmutil to x/precisebank. It transfers the reserve balance from x/evmutil
|
||||||
|
// to x/precisebank and ensures that the reserve fully backs all fractional
|
||||||
|
// balances. It mints or burns coins to back the fractional balances exactly.
|
||||||
|
func TransferFractionalBalanceReserve(
|
||||||
|
ctx sdk.Context,
|
||||||
|
accountKeeper evmutiltypes.AccountKeeper,
|
||||||
|
bankKeeper bankkeeper.Keeper,
|
||||||
|
precisebankKeeper precisebankkeeper.Keeper,
|
||||||
|
) error {
|
||||||
|
logger := ctx.Logger()
|
||||||
|
|
||||||
|
// Transfer x/evmutil reserve to x/precisebank.
|
||||||
|
evmutilAddr := accountKeeper.GetModuleAddress(evmutiltypes.ModuleName)
|
||||||
|
reserveBalance := bankKeeper.GetBalance(ctx, evmutilAddr, precisebanktypes.IntegerCoinDenom)
|
||||||
|
|
||||||
|
if err := bankKeeper.SendCoinsFromModuleToModule(
|
||||||
|
ctx,
|
||||||
|
evmutiltypes.ModuleName, // from x/evmutil
|
||||||
|
precisebanktypes.ModuleName, // to x/precisebank
|
||||||
|
sdk.NewCoins(reserveBalance),
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("failed to transfer reserve from x/evmutil to x/precisebank: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(fmt.Sprintf("transferred reserve balance: %s", reserveBalance))
|
||||||
|
|
||||||
|
// Ensure x/precisebank reserve fully backs all fractional balances.
|
||||||
|
totalFractionalBalances := precisebankKeeper.GetTotalSumFractionalBalances(ctx)
|
||||||
|
|
||||||
|
// Does NOT ensure state is correct, total fractional balances should be a
|
||||||
|
// multiple of conversion factor but is not guaranteed due to the remainder.
|
||||||
|
// Remainder initialization is handled by InitializeRemainder.
|
||||||
|
|
||||||
|
// Determine how much the reserve is off by, e.g. unbacked amount
|
||||||
|
expectedReserveBalance := totalFractionalBalances.Quo(precisebanktypes.ConversionFactor())
|
||||||
|
|
||||||
|
// If there is a remainder (totalFractionalBalances % conversionFactor != 0),
|
||||||
|
// then expectedReserveBalance is rounded up to the nearest integer.
|
||||||
|
if totalFractionalBalances.Mod(precisebanktypes.ConversionFactor()).IsPositive() {
|
||||||
|
expectedReserveBalance = expectedReserveBalance.Add(sdkmath.OneInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
unbackedAmount := expectedReserveBalance.Sub(reserveBalance.Amount)
|
||||||
|
logger.Info(fmt.Sprintf("total account fractional balances: %s", totalFractionalBalances))
|
||||||
|
|
||||||
|
// Three possible cases:
|
||||||
|
// 1. Reserve is not enough, mint coins to back the fractional balances
|
||||||
|
// 2. Reserve is too much, burn coins to back the fractional balances exactly
|
||||||
|
// 3. Reserve is exactly enough, no action needed
|
||||||
|
if unbackedAmount.IsPositive() {
|
||||||
|
coins := sdk.NewCoins(sdk.NewCoin(precisebanktypes.IntegerCoinDenom, unbackedAmount))
|
||||||
|
if err := bankKeeper.MintCoins(ctx, precisebanktypes.ModuleName, coins); err != nil {
|
||||||
|
return fmt.Errorf("failed to mint extra reserve coins: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(fmt.Sprintf("unbacked amount minted to reserve: %s", unbackedAmount))
|
||||||
|
} else if unbackedAmount.IsNegative() {
|
||||||
|
coins := sdk.NewCoins(sdk.NewCoin(precisebanktypes.IntegerCoinDenom, unbackedAmount.Neg()))
|
||||||
|
if err := bankKeeper.BurnCoins(ctx, precisebanktypes.ModuleName, coins); err != nil {
|
||||||
|
return fmt.Errorf("failed to burn extra reserve coins: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(fmt.Sprintf("extra reserve amount burned: %s", unbackedAmount.Neg()))
|
||||||
|
} else {
|
||||||
|
logger.Info("reserve exactly backs fractional balances, no mint/burn needed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
434
app/upgrades_test.go
Normal file
434
app/upgrades_test.go
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
package app_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/kava-labs/kava/app"
|
||||||
|
evmutiltypes "github.com/kava-labs/kava/x/evmutil/types"
|
||||||
|
precisebankkeeper "github.com/kava-labs/kava/x/precisebank/keeper"
|
||||||
|
precisebanktypes "github.com/kava-labs/kava/x/precisebank/types"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMigrateEvmutilToPrecisebank(t *testing.T) {
|
||||||
|
// Full test case with all components together
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialReserve sdkmath.Int
|
||||||
|
fractionalBalances []sdkmath.Int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"no fractional balances",
|
||||||
|
sdkmath.NewInt(0),
|
||||||
|
[]sdkmath.Int{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sufficient reserve, 0 remainder",
|
||||||
|
// Accounts adding up to 2 int units, same as reserve
|
||||||
|
sdkmath.NewInt(2),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"insufficient reserve, 0 remainder",
|
||||||
|
// Accounts adding up to 2 int units, but only 1 int unit in reserve
|
||||||
|
sdkmath.NewInt(1),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"excess reserve, 0 remainder",
|
||||||
|
// Accounts adding up to 2 int units, but 3 int unit in reserve
|
||||||
|
sdkmath.NewInt(3),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sufficient reserve, non-zero remainder",
|
||||||
|
// Accounts adding up to 1.5 int units, same as reserve
|
||||||
|
sdkmath.NewInt(2),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"insufficient reserve, non-zero remainder",
|
||||||
|
// Accounts adding up to 1.5 int units, less than reserve,
|
||||||
|
// Reserve should be 2 and remainder 0.5
|
||||||
|
sdkmath.NewInt(1),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"excess reserve, non-zero remainder",
|
||||||
|
// Accounts adding up to 1.5 int units, 3 int units in reserve
|
||||||
|
sdkmath.NewInt(3),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
tApp.InitializeFromGenesisStates()
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: time.Now()})
|
||||||
|
|
||||||
|
ak := tApp.GetAccountKeeper()
|
||||||
|
bk := tApp.GetBankKeeper()
|
||||||
|
evmuk := tApp.GetEvmutilKeeper()
|
||||||
|
pbk := tApp.GetPrecisebankKeeper()
|
||||||
|
|
||||||
|
reserveCoin := sdk.NewCoin(precisebanktypes.IntegerCoinDenom, tt.initialReserve)
|
||||||
|
err := bk.MintCoins(ctx, evmutiltypes.ModuleName, sdk.NewCoins(reserveCoin))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
oldReserveAddr := tApp.GetAccountKeeper().GetModuleAddress(evmutiltypes.ModuleName)
|
||||||
|
newReserveAddr := tApp.GetAccountKeeper().GetModuleAddress(precisebanktypes.ModuleName)
|
||||||
|
|
||||||
|
// Double check balances
|
||||||
|
oldReserveBalance := bk.GetBalance(ctx, oldReserveAddr, precisebanktypes.IntegerCoinDenom)
|
||||||
|
newReserveBalance := bk.GetBalance(ctx, newReserveAddr, precisebanktypes.IntegerCoinDenom)
|
||||||
|
|
||||||
|
require.Equal(t, tt.initialReserve, oldReserveBalance.Amount, "initial x/evmutil reserve balance")
|
||||||
|
require.True(t, newReserveBalance.IsZero(), "empty initial new reserve")
|
||||||
|
|
||||||
|
// Set accounts
|
||||||
|
for i, balance := range tt.fractionalBalances {
|
||||||
|
addr := sdk.AccAddress([]byte(strconv.Itoa(i)))
|
||||||
|
|
||||||
|
err := evmuk.SetBalance(ctx, addr, balance)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run full x/evmutil -> x/precisebank migration
|
||||||
|
err = app.MigrateEvmutilToPrecisebank(
|
||||||
|
ctx,
|
||||||
|
ak,
|
||||||
|
bk,
|
||||||
|
evmuk,
|
||||||
|
pbk,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check old reserve is empty
|
||||||
|
oldReserveBalanceAfter := bk.GetBalance(ctx, oldReserveAddr, precisebanktypes.IntegerCoinDenom)
|
||||||
|
require.True(t, oldReserveBalanceAfter.IsZero(), "old reserve should be empty")
|
||||||
|
|
||||||
|
// Check new reserve fully backs fractional balances
|
||||||
|
newReserveBalanceAfter := bk.GetBalance(ctx, newReserveAddr, precisebanktypes.IntegerCoinDenom)
|
||||||
|
fractionalBalanceTotal := pbk.GetTotalSumFractionalBalances(ctx)
|
||||||
|
remainder := pbk.GetRemainderAmount(ctx)
|
||||||
|
|
||||||
|
expectedReserveBal := fractionalBalanceTotal.Add(remainder)
|
||||||
|
require.Equal(
|
||||||
|
t,
|
||||||
|
expectedReserveBal,
|
||||||
|
newReserveBalanceAfter.Amount.Mul(precisebanktypes.ConversionFactor()),
|
||||||
|
"new reserve should equal total fractional balances",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check balances are deleted in evmutil and migrated to precisebank
|
||||||
|
for i := range tt.fractionalBalances {
|
||||||
|
addr := sdk.AccAddress([]byte(strconv.Itoa(i)))
|
||||||
|
acc := evmuk.GetAccount(ctx, addr)
|
||||||
|
require.Nil(t, acc, "account should be deleted")
|
||||||
|
|
||||||
|
balance := pbk.GetFractionalBalance(ctx, addr)
|
||||||
|
require.Equal(t, tt.fractionalBalances[i], balance, "balance should be migrated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks balances valid and remainder
|
||||||
|
res, stop := precisebankkeeper.AllInvariants(pbk)(ctx)
|
||||||
|
require.Falsef(t, stop, "invariants should pass: %s", res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransferFractionalBalances(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fractionalBalances []sdkmath.Int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"no fractional balances",
|
||||||
|
[]sdkmath.Int{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"balanced fractional balances",
|
||||||
|
[]sdkmath.Int{
|
||||||
|
// 4 accounts
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unbalanced balances",
|
||||||
|
[]sdkmath.Int{
|
||||||
|
// 3 accounts
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
tApp.InitializeFromGenesisStates()
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: time.Now()})
|
||||||
|
|
||||||
|
evmutilk := tApp.GetEvmutilKeeper()
|
||||||
|
pbk := tApp.GetPrecisebankKeeper()
|
||||||
|
|
||||||
|
for i, balance := range tt.fractionalBalances {
|
||||||
|
addr := sdk.AccAddress([]byte(strconv.Itoa(i)))
|
||||||
|
|
||||||
|
err := evmutilk.SetBalance(ctx, addr, balance)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run balance transfer
|
||||||
|
aggregateSum, err := app.TransferFractionalBalances(
|
||||||
|
ctx,
|
||||||
|
evmutilk,
|
||||||
|
pbk,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check balances are deleted in evmutil and migrated to precisebank
|
||||||
|
sum := sdkmath.ZeroInt()
|
||||||
|
for i := range tt.fractionalBalances {
|
||||||
|
sum = sum.Add(tt.fractionalBalances[i])
|
||||||
|
|
||||||
|
addr := sdk.AccAddress([]byte(strconv.Itoa(i)))
|
||||||
|
acc := evmutilk.GetAccount(ctx, addr)
|
||||||
|
require.Nil(t, acc, "account should be deleted")
|
||||||
|
|
||||||
|
balance := pbk.GetFractionalBalance(ctx, addr)
|
||||||
|
require.Equal(t, tt.fractionalBalances[i], balance, "balance should be migrated")
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, sum, aggregateSum, "aggregate sum should be correct")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitializeRemainder(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
giveAggregateSum sdkmath.Int
|
||||||
|
wantRemainder sdkmath.Int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"0 remainder, 1ukava",
|
||||||
|
precisebanktypes.ConversionFactor(),
|
||||||
|
sdkmath.NewInt(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0 remainder, multiple ukava",
|
||||||
|
precisebanktypes.ConversionFactor().MulRaw(5),
|
||||||
|
sdkmath.NewInt(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non-zero remainder, min",
|
||||||
|
precisebanktypes.ConversionFactor().SubRaw(1),
|
||||||
|
sdkmath.NewInt(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non-zero remainder, max",
|
||||||
|
sdkmath.NewInt(1),
|
||||||
|
precisebanktypes.ConversionFactor().SubRaw(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non-zero remainder, half",
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
tApp.InitializeFromGenesisStates()
|
||||||
|
|
||||||
|
pbk := tApp.GetPrecisebankKeeper()
|
||||||
|
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: time.Now()})
|
||||||
|
|
||||||
|
remainder := app.InitializeRemainder(
|
||||||
|
ctx,
|
||||||
|
tApp.GetPrecisebankKeeper(),
|
||||||
|
tt.giveAggregateSum,
|
||||||
|
)
|
||||||
|
require.Equal(t, tt.wantRemainder, remainder)
|
||||||
|
|
||||||
|
// Check actual state
|
||||||
|
remainderAfter := pbk.GetRemainderAmount(ctx)
|
||||||
|
require.Equal(t, tt.wantRemainder, remainderAfter)
|
||||||
|
|
||||||
|
// Not checking invariants here since it requires actual balance state
|
||||||
|
aggregateSumWithRemainder := tt.giveAggregateSum.Add(remainder)
|
||||||
|
require.True(
|
||||||
|
t,
|
||||||
|
aggregateSumWithRemainder.
|
||||||
|
Mod(precisebanktypes.ConversionFactor()).
|
||||||
|
IsZero(),
|
||||||
|
"remainder + aggregate sum should be a multiple of the conversion factor",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransferFractionalBalanceReserve(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialReserve sdk.Coin
|
||||||
|
fractionalBalances []sdkmath.Int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"balanced reserve, no remainder",
|
||||||
|
sdk.NewCoin(precisebanktypes.IntegerCoinDenom, sdk.NewInt(1)),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
// 2 accounts
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"insufficient reserve",
|
||||||
|
sdk.NewCoin(precisebanktypes.IntegerCoinDenom, sdk.NewInt(1)),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
// 4 accounts, total 2 int units
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extra reserve funds",
|
||||||
|
sdk.NewCoin(precisebanktypes.IntegerCoinDenom, sdk.NewInt(2)),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
// 2 accounts, total 1 int units
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"insufficient reserve, with remainder",
|
||||||
|
sdk.NewCoin(precisebanktypes.IntegerCoinDenom, sdk.NewInt(1)),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
// 5 accounts, total 2.5 int units
|
||||||
|
// Expected 3 int units in reserve, 0.5 remainder
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extra reserve funds, with remainder",
|
||||||
|
sdk.NewCoin(precisebanktypes.IntegerCoinDenom, sdk.NewInt(3)),
|
||||||
|
[]sdkmath.Int{
|
||||||
|
// 3 accounts, total 1.5 int units.
|
||||||
|
// Expected 2 int units in reserve, 0.5 remainder
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
precisebanktypes.ConversionFactor().QuoRaw(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
tApp.InitializeFromGenesisStates()
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: time.Now()})
|
||||||
|
|
||||||
|
bk := tApp.GetBankKeeper()
|
||||||
|
pbk := tApp.GetPrecisebankKeeper()
|
||||||
|
err := bk.MintCoins(ctx, evmutiltypes.ModuleName, sdk.NewCoins(tt.initialReserve))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
oldReserveAddr := tApp.GetAccountKeeper().GetModuleAddress(evmutiltypes.ModuleName)
|
||||||
|
newReserveAddr := tApp.GetAccountKeeper().GetModuleAddress(precisebanktypes.ModuleName)
|
||||||
|
|
||||||
|
// Double check balances
|
||||||
|
oldReserveBalance := bk.GetBalance(ctx, oldReserveAddr, precisebanktypes.IntegerCoinDenom)
|
||||||
|
newReserveBalance := bk.GetBalance(ctx, newReserveAddr, precisebanktypes.IntegerCoinDenom)
|
||||||
|
|
||||||
|
require.Equal(t, tt.initialReserve, oldReserveBalance)
|
||||||
|
require.True(t, newReserveBalance.IsZero(), "empty initial new reserve")
|
||||||
|
|
||||||
|
for i, balance := range tt.fractionalBalances {
|
||||||
|
addr := sdk.AccAddress([]byte{byte(i)})
|
||||||
|
|
||||||
|
require.NotPanics(t, func() {
|
||||||
|
pbk.SetFractionalBalance(ctx, addr, balance)
|
||||||
|
}, "given fractional balances should be valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run reserve migration
|
||||||
|
err = app.TransferFractionalBalanceReserve(
|
||||||
|
ctx,
|
||||||
|
tApp.GetAccountKeeper(),
|
||||||
|
bk,
|
||||||
|
tApp.GetPrecisebankKeeper(),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check old reserve is empty
|
||||||
|
oldReserveBalanceAfter := bk.GetBalance(ctx, oldReserveAddr, precisebanktypes.IntegerCoinDenom)
|
||||||
|
require.True(t, oldReserveBalanceAfter.IsZero(), "old reserve should be empty")
|
||||||
|
|
||||||
|
// Check new reserve fully backs fractional balances
|
||||||
|
newReserveBalanceAfter := bk.GetBalance(ctx, newReserveAddr, precisebanktypes.IntegerCoinDenom)
|
||||||
|
fractionalBalanceTotal := pbk.GetTotalSumFractionalBalances(ctx)
|
||||||
|
|
||||||
|
expectedReserveBal := fractionalBalanceTotal.
|
||||||
|
Quo(precisebanktypes.ConversionFactor())
|
||||||
|
|
||||||
|
// Check if theres a remainder
|
||||||
|
if fractionalBalanceTotal.Mod(precisebanktypes.ConversionFactor()).IsPositive() {
|
||||||
|
expectedReserveBal = expectedReserveBal.Add(sdkmath.OneInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(
|
||||||
|
t,
|
||||||
|
expectedReserveBal,
|
||||||
|
newReserveBalanceAfter.Amount,
|
||||||
|
"new reserve should equal total fractional balances + remainder",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -19,14 +19,14 @@ E2E_SKIP_SHUTDOWN=false
|
|||||||
|
|
||||||
# The following variables should be defined to run an upgrade.
|
# The following variables should be defined to run an upgrade.
|
||||||
# E2E_INCLUDE_AUTOMATED_UPGRADE when true enables the automated upgrade & corresponding tests in the suite.
|
# E2E_INCLUDE_AUTOMATED_UPGRADE when true enables the automated upgrade & corresponding tests in the suite.
|
||||||
E2E_INCLUDE_AUTOMATED_UPGRADE=false
|
E2E_INCLUDE_AUTOMATED_UPGRADE=true
|
||||||
# E2E_KAVA_UPGRADE_NAME is the name of the upgrade that must be in the current local image.
|
# E2E_KAVA_UPGRADE_NAME is the name of the upgrade that must be in the current local image.
|
||||||
E2E_KAVA_UPGRADE_NAME=
|
E2E_KAVA_UPGRADE_NAME=v0.27.0
|
||||||
# E2E_KAVA_UPGRADE_HEIGHT is the height at which the upgrade will be applied.
|
# E2E_KAVA_UPGRADE_HEIGHT is the height at which the upgrade will be applied.
|
||||||
# If IBC tests are enabled this should be >30. Otherwise, this should be >10.
|
# If IBC tests are enabled this should be >30. Otherwise, this should be >10.
|
||||||
E2E_KAVA_UPGRADE_HEIGHT=
|
E2E_KAVA_UPGRADE_HEIGHT=35
|
||||||
# E2E_KAVA_UPGRADE_BASE_IMAGE_TAG is the tag of the docker image the chain should upgrade from.
|
# E2E_KAVA_UPGRADE_BASE_IMAGE_TAG is the tag of the docker image the chain should upgrade from.
|
||||||
E2E_KAVA_UPGRADE_BASE_IMAGE_TAG=
|
E2E_KAVA_UPGRADE_BASE_IMAGE_TAG=v0.26.0-goleveldb
|
||||||
|
|
||||||
# E2E_KAVA_ERC20_ADDRESS is the address of a pre-deployed ERC20 token with the following properties:
|
# E2E_KAVA_ERC20_ADDRESS is the address of a pre-deployed ERC20 token with the following properties:
|
||||||
# - the E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC has nonzero balance
|
# - the E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC has nonzero balance
|
||||||
|
@ -1,18 +1,102 @@
|
|||||||
package e2e_test
|
package e2e_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||||
|
precisebanktypes "github.com/kava-labs/kava/x/precisebank/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestUpgradeHandler can be used to run tests post-upgrade. If an upgrade is enabled, all tests
|
func (suite *IntegrationTestSuite) TestUpgrade_PreciseBankReserveTransfer() {
|
||||||
// are run against the upgraded chain. However, this file is a good place to consolidate all
|
|
||||||
// acceptance tests for a given set of upgrade handlers.
|
|
||||||
func (suite *IntegrationTestSuite) TestUpgradeHandler() {
|
|
||||||
suite.SkipIfUpgradeDisabled()
|
suite.SkipIfUpgradeDisabled()
|
||||||
fmt.Println("An upgrade has run!")
|
|
||||||
suite.True(true)
|
|
||||||
|
|
||||||
// Uncomment & use these contexts to compare chain state before & after the upgrade occurs.
|
beforeUpgradeCtx := suite.Kava.Grpc.CtxAtHeight(suite.UpgradeHeight - 1)
|
||||||
// beforeUpgradeCtx := suite.Kava.Grpc.CtxAtHeight(suite.UpgradeHeight - 1)
|
afterUpgradeCtx := suite.Kava.Grpc.CtxAtHeight(suite.UpgradeHeight)
|
||||||
// afterUpgradeCtx := suite.Kava.Grpc.CtxAtHeight(suite.UpgradeHeight)
|
|
||||||
|
grpcClient := suite.Kava.Grpc
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Get initial reserve balances
|
||||||
|
evmutilAddr := "kava1w9vxuke5dz6hyza2j932qgmxltnfxwl78u920k"
|
||||||
|
precisebankAddr := "kava12yfe2jaupmtjruwxsec7hg7er60fhaa4uz7ffl"
|
||||||
|
|
||||||
|
previousEvmutilBalRes, err := grpcClient.Query.Bank.Balance(beforeUpgradeCtx, &banktypes.QueryBalanceRequest{
|
||||||
|
Address: evmutilAddr,
|
||||||
|
Denom: precisebanktypes.IntegerCoinDenom,
|
||||||
|
})
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Require().NotNil(previousEvmutilBalRes.Balance)
|
||||||
|
suite.Require().True(
|
||||||
|
previousEvmutilBalRes.Balance.Amount.IsPositive(),
|
||||||
|
"should have reserve balance before upgrade",
|
||||||
|
)
|
||||||
|
|
||||||
|
previousPrecisebankBalRes, err := grpcClient.Query.Bank.Balance(beforeUpgradeCtx, &banktypes.QueryBalanceRequest{
|
||||||
|
Address: precisebankAddr,
|
||||||
|
Denom: precisebanktypes.IntegerCoinDenom,
|
||||||
|
})
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Require().NotNil(previousPrecisebankBalRes.Balance)
|
||||||
|
suite.Require().True(
|
||||||
|
previousPrecisebankBalRes.Balance.Amount.IsZero(),
|
||||||
|
"should be empty before upgrade",
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.T().Logf("x/evmutil balances before upgrade: %s", previousEvmutilBalRes.Balance)
|
||||||
|
suite.T().Logf("x/precisebank balances before upgrade: %s", previousPrecisebankBalRes.Balance)
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// After upgrade
|
||||||
|
// - Check reserve balance transfer
|
||||||
|
// - Check reserve fully backs fractional amounts
|
||||||
|
afterEvmutilBalRes, err := grpcClient.Query.Bank.Balance(afterUpgradeCtx, &banktypes.QueryBalanceRequest{
|
||||||
|
Address: evmutilAddr,
|
||||||
|
Denom: precisebanktypes.IntegerCoinDenom,
|
||||||
|
})
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Require().NotNil(afterEvmutilBalRes.Balance)
|
||||||
|
suite.Require().Truef(
|
||||||
|
afterEvmutilBalRes.Balance.Amount.IsZero(),
|
||||||
|
"should have transferred all reserve balance to precisebank, expected 0 but got %s",
|
||||||
|
afterEvmutilBalRes.Balance,
|
||||||
|
)
|
||||||
|
|
||||||
|
afterPrecisebankBalRes, err := grpcClient.Query.Bank.Balance(afterUpgradeCtx, &banktypes.QueryBalanceRequest{
|
||||||
|
Address: precisebankAddr,
|
||||||
|
Denom: precisebanktypes.IntegerCoinDenom,
|
||||||
|
})
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Require().NotNil(afterPrecisebankBalRes.Balance)
|
||||||
|
// 2 total in reserve- genesis.json has 5 accounts with fractional balances
|
||||||
|
// totalling 2 integer coins
|
||||||
|
suite.Require().Equal(int64(2), afterPrecisebankBalRes.Balance.Amount.Int64())
|
||||||
|
|
||||||
|
suite.T().Logf("x/evmutil balances after upgrade: %s", afterEvmutilBalRes.Balance)
|
||||||
|
suite.T().Logf("x/precisebank balances after upgrade: %s", afterPrecisebankBalRes.Balance)
|
||||||
|
|
||||||
|
sumFractional, err := grpcClient.Query.Precisebank.TotalFractionalBalances(
|
||||||
|
afterUpgradeCtx,
|
||||||
|
&precisebanktypes.QueryTotalFractionalBalancesRequest{},
|
||||||
|
)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
suite.Require().Equal(
|
||||||
|
sumFractional.Total.Amount,
|
||||||
|
afterPrecisebankBalRes.Balance.Amount.Mul(precisebanktypes.ConversionFactor()),
|
||||||
|
"reserve should match exactly sum fractional balances",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check remainder + total fractional balances = reserve balance
|
||||||
|
remainderRes, err := grpcClient.Query.Precisebank.Remainder(
|
||||||
|
afterUpgradeCtx,
|
||||||
|
&precisebanktypes.QueryRemainderRequest{},
|
||||||
|
)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
sumFractionalAndRemainder := sumFractional.Total.Add(remainderRes.Remainder)
|
||||||
|
reserveBalanceExtended := afterPrecisebankBalRes.Balance.Amount.Mul(precisebanktypes.ConversionFactor())
|
||||||
|
|
||||||
|
suite.Require().Equal(
|
||||||
|
sumFractionalAndRemainder.Amount,
|
||||||
|
reserveBalanceExtended,
|
||||||
|
"remainder + sum(fractional balances) should be = reserve balance",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user