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:
drklee3 2024-07-25 15:48:23 -07:00
parent 65d091d458
commit 493ce0516f
No known key found for this signature in database
GPG Key ID: A8F9A6E3133E0F6A
5 changed files with 805 additions and 16 deletions

View File

@ -348,7 +348,7 @@ start-remote-sims:
update-kvtool:
git submodule init || true
git submodule update
git submodule update --remote
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

View File

@ -1,3 +1,274 @@
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
View 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",
)
})
}
}

View File

@ -19,14 +19,14 @@ E2E_SKIP_SHUTDOWN=false
# 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=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=
E2E_KAVA_UPGRADE_NAME=v0.27.0
# 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.
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=
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:
# - the E2E_KAVA_FUNDED_ACCOUNT_MNEMONIC has nonzero balance

View File

@ -1,18 +1,102 @@
package e2e_test
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
// 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() {
func (suite *IntegrationTestSuite) TestUpgrade_PreciseBankReserveTransfer() {
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)
// afterUpgradeCtx := suite.Kava.Grpc.CtxAtHeight(suite.UpgradeHeight)
beforeUpgradeCtx := suite.Kava.Grpc.CtxAtHeight(suite.UpgradeHeight - 1)
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",
)
}