mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-14 03:55:19 +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:
|
||||
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
|
||||
|
273
app/upgrades.go
273
app/upgrades.go
@ -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
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.
|
||||
# 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
|
||||
|
@ -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",
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user