741 lines
33 KiB
Go
741 lines
33 KiB
Go
package staking
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"gotest.tools/v3/assert"
|
|
|
|
"cosmossdk.io/collections"
|
|
"cosmossdk.io/core/header"
|
|
"cosmossdk.io/math"
|
|
_ "cosmossdk.io/x/accounts"
|
|
banktestutil "cosmossdk.io/x/bank/testutil"
|
|
_ "cosmossdk.io/x/consensus"
|
|
_ "cosmossdk.io/x/distribution"
|
|
_ "cosmossdk.io/x/protocolpool"
|
|
_ "cosmossdk.io/x/slashing"
|
|
"cosmossdk.io/x/staking/keeper"
|
|
"cosmossdk.io/x/staking/testutil"
|
|
"cosmossdk.io/x/staking/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec/address"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
|
"github.com/cosmos/cosmos-sdk/tests/integration/v2"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
|
)
|
|
|
|
// bootstrapSlashTest creates 3 validators and bootstrap the app.
|
|
func bootstrapSlashTest(t *testing.T, power int64) (*fixture, []sdk.AccAddress, []sdk.ValAddress) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
f := initFixture(t, false)
|
|
|
|
addrDels, addrVals := generateAddresses(f, 100)
|
|
|
|
amt := f.stakingKeeper.TokensFromConsensusPower(f.ctx, power)
|
|
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
|
|
require.NoError(t, err)
|
|
totalSupply := sdk.NewCoins(sdk.NewCoin(bondDenom, amt.MulRaw(int64(len(addrDels)))))
|
|
|
|
notBondedPool := f.stakingKeeper.GetNotBondedPool(f.ctx)
|
|
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, notBondedPool.GetName(), totalSupply))
|
|
|
|
f.accountKeeper.SetModuleAccount(f.ctx, notBondedPool)
|
|
|
|
numVals := int64(3)
|
|
bondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, amt.MulRaw(numVals)))
|
|
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
|
|
|
|
// set bonded pool balance
|
|
f.accountKeeper.SetModuleAccount(f.ctx, bondedPool)
|
|
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, bondedPool.GetName(), bondedCoins))
|
|
|
|
for i := int64(0); i < numVals; i++ {
|
|
validator := testutil.NewValidator(t, addrVals[i], PKs[i])
|
|
validator, _ = validator.AddTokensFromDel(amt)
|
|
validator, _ = keeper.TestingUpdateValidatorV2(f.stakingKeeper, f.ctx, validator, true)
|
|
assert.NilError(t, f.stakingKeeper.SetValidatorByConsAddr(f.ctx, validator))
|
|
}
|
|
|
|
return f, addrDels, addrVals
|
|
}
|
|
|
|
// tests slashUnbondingDelegation
|
|
func TestSlashUnbondingDelegation(t *testing.T) {
|
|
f, addrDels, addrVals := bootstrapSlashTest(t, 10)
|
|
|
|
fraction := math.LegacyNewDecWithPrec(5, 1)
|
|
|
|
// set an unbonding delegation with expiration timestamp (beyond which the
|
|
// unbonding delegation shouldn't be slashed)
|
|
ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 0,
|
|
time.Unix(5, 0), math.NewInt(10), address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
|
|
|
|
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(f.ctx, ubd))
|
|
|
|
// unbonding started prior to the infraction height, stakw didn't contribute
|
|
slashAmount, err := f.stakingKeeper.SlashUnbondingDelegation(f.ctx, ubd, 1, fraction)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, slashAmount.Equal(math.NewInt(0)))
|
|
|
|
// after the expiration time, no longer eligible for slashing
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Time: time.Unix(10, 0)})
|
|
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(f.ctx, ubd))
|
|
slashAmount, err = f.stakingKeeper.SlashUnbondingDelegation(f.ctx, ubd, 0, fraction)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, slashAmount.Equal(math.NewInt(0)))
|
|
|
|
// test valid slash, before expiration timestamp and to which stake contributed
|
|
notBondedPool := f.stakingKeeper.GetNotBondedPool(f.ctx)
|
|
oldUnbondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, notBondedPool.GetAddress())
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Time: time.Unix(0, 0)})
|
|
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(f.ctx, ubd))
|
|
slashAmount, err = f.stakingKeeper.SlashUnbondingDelegation(f.ctx, ubd, 0, fraction)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, slashAmount.Equal(math.NewInt(5)))
|
|
ubd, found := f.stakingKeeper.GetUnbondingDelegation(f.ctx, addrDels[0], addrVals[0])
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(ubd.Entries) == 1)
|
|
|
|
// initial balance unchanged
|
|
assert.DeepEqual(t, math.NewInt(10), ubd.Entries[0].InitialBalance)
|
|
|
|
// balance decreased
|
|
assert.DeepEqual(t, math.NewInt(5), ubd.Entries[0].Balance)
|
|
newUnbondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, notBondedPool.GetAddress())
|
|
diffTokens := oldUnbondedPoolBalances.Sub(newUnbondedPoolBalances...)
|
|
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, diffTokens.AmountOf(bondDenom).Equal(math.NewInt(5)))
|
|
}
|
|
|
|
// tests slashRedelegation
|
|
func TestSlashRedelegation(t *testing.T) {
|
|
f, addrDels, addrVals := bootstrapSlashTest(t, 10)
|
|
fraction := math.LegacyNewDecWithPrec(5, 1)
|
|
|
|
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
|
|
assert.NilError(t, err)
|
|
|
|
// add bonded tokens to pool for (re)delegations
|
|
startCoins := sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 15))
|
|
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
|
|
_ = f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
|
|
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, bondedPool.GetName(), startCoins))
|
|
f.accountKeeper.SetModuleAccount(f.ctx, bondedPool)
|
|
|
|
// set a redelegation with an expiration timestamp beyond which the
|
|
// redelegation shouldn't be slashed
|
|
rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0,
|
|
time.Unix(5, 0), math.NewInt(10), math.LegacyNewDec(10), address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
|
|
|
|
assert.NilError(t, f.stakingKeeper.SetRedelegation(f.ctx, rd))
|
|
|
|
// set the associated delegation
|
|
del := types.NewDelegation(addrDels[0].String(), addrVals[1].String(), math.LegacyNewDec(10))
|
|
assert.NilError(t, f.stakingKeeper.SetDelegation(f.ctx, del))
|
|
|
|
// started redelegating prior to the current height, stake didn't contribute to infraction
|
|
validator, found := f.stakingKeeper.GetValidator(f.ctx, addrVals[1])
|
|
assert.Assert(t, found)
|
|
slashAmount, err := f.stakingKeeper.SlashRedelegation(f.ctx, validator, rd, 1, fraction)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, slashAmount.Equal(math.NewInt(0)))
|
|
|
|
// after the expiration time, no longer eligible for slashing
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Time: time.Unix(10, 0)})
|
|
assert.NilError(t, f.stakingKeeper.SetRedelegation(f.ctx, rd))
|
|
validator, found = f.stakingKeeper.GetValidator(f.ctx, addrVals[1])
|
|
assert.Assert(t, found)
|
|
slashAmount, err = f.stakingKeeper.SlashRedelegation(f.ctx, validator, rd, 0, fraction)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, slashAmount.Equal(math.NewInt(0)))
|
|
|
|
balances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
|
|
// test valid slash, before expiration timestamp and to which stake contributed
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Time: time.Unix(0, 0)})
|
|
assert.NilError(t, f.stakingKeeper.SetRedelegation(f.ctx, rd))
|
|
validator, found = f.stakingKeeper.GetValidator(f.ctx, addrVals[1])
|
|
assert.Assert(t, found)
|
|
slashAmount, err = f.stakingKeeper.SlashRedelegation(f.ctx, validator, rd, 0, fraction)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, slashAmount.Equal(math.NewInt(5)))
|
|
rd, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(rd.Entries) == 1)
|
|
|
|
// end block
|
|
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, 1)
|
|
|
|
// initialbalance unchanged
|
|
assert.DeepEqual(t, math.NewInt(10), rd.Entries[0].InitialBalance)
|
|
|
|
// shares decreased
|
|
del, found = f.stakingKeeper.Delegations.Get(f.ctx, collections.Join(addrDels[0], addrVals[1]))
|
|
assert.Assert(t, found)
|
|
assert.Equal(t, int64(5), del.Shares.RoundInt64())
|
|
|
|
// pool bonded tokens should decrease
|
|
burnedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, slashAmount))
|
|
assert.DeepEqual(t, balances.Sub(burnedCoins...), f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress()))
|
|
}
|
|
|
|
// test slash at a negative height
|
|
// this just represents pre-genesis and should have the same effect as slashing at height 0
|
|
func TestSlashAtNegativeHeight(t *testing.T) {
|
|
f, _, _ := bootstrapSlashTest(t, 10)
|
|
consAddr := sdk.ConsAddress(PKs[0].Address())
|
|
fraction := math.LegacyNewDecWithPrec(5, 1)
|
|
|
|
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
|
|
oldBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
|
|
_, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
_, err := f.stakingKeeper.Slash(f.ctx, consAddr, -2, 10, fraction)
|
|
assert.NilError(t, err)
|
|
|
|
// read updated state
|
|
validator, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
|
|
// end block
|
|
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, 1)
|
|
|
|
valbz, err := f.stakingKeeper.ValidatorAddressCodec().StringToBytes(validator.GetOperator())
|
|
assert.NilError(t, err)
|
|
|
|
validator, found = f.stakingKeeper.GetValidator(f.ctx, valbz)
|
|
assert.Assert(t, found)
|
|
// power decreased
|
|
assert.Equal(t, int64(5), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
|
|
|
|
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
|
|
assert.NilError(t, err)
|
|
|
|
// pool bonded shares decreased
|
|
newBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
|
|
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 5).String(), diffTokens.String())
|
|
}
|
|
|
|
// tests Slash at the current height
|
|
func TestSlashValidatorAtCurrentHeight(t *testing.T) {
|
|
f, _, _ := bootstrapSlashTest(t, 10)
|
|
consAddr := sdk.ConsAddress(PKs[0].Address())
|
|
fraction := math.LegacyNewDecWithPrec(5, 1)
|
|
|
|
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
|
|
assert.NilError(t, err)
|
|
|
|
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
|
|
oldBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
|
|
_, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, int64(f.app.LastBlockHeight()), 10, fraction)
|
|
assert.NilError(t, err)
|
|
|
|
// read updated state
|
|
validator, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
|
|
// end block
|
|
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, 1)
|
|
|
|
valbz, err := f.stakingKeeper.ValidatorAddressCodec().StringToBytes(validator.GetOperator())
|
|
assert.NilError(t, err)
|
|
|
|
validator, found = f.stakingKeeper.GetValidator(f.ctx, valbz)
|
|
assert.Assert(t, found)
|
|
// power decreased
|
|
assert.Equal(t, int64(5), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
|
|
|
|
// pool bonded shares decreased
|
|
newBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
|
|
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 5).String(), diffTokens.String())
|
|
}
|
|
|
|
// TestSlashWithUnbondingDelegation tests the slashing of a validator with an unbonding delegation.
|
|
// It sets up an environment with a validator and an unbonding delegation, and then performs slashing
|
|
// operations on the validator. The test verifies that the slashing correctly affects the unbonding
|
|
// delegation and the validator's power.
|
|
func TestSlashWithUnbondingDelegation(t *testing.T) {
|
|
f, addrDels, addrVals := bootstrapSlashTest(t, 10)
|
|
|
|
consAddr := sdk.ConsAddress(PKs[0].Address())
|
|
fraction := math.LegacyNewDecWithPrec(5, 1)
|
|
|
|
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
|
|
assert.NilError(t, err)
|
|
|
|
// set an unbonding delegation with expiration timestamp beyond which the
|
|
// unbonding delegation shouldn't be slashed
|
|
ubdTokens := f.stakingKeeper.TokensFromConsensusPower(f.ctx, 4)
|
|
ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11, time.Unix(0, 0), ubdTokens, address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
|
|
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(f.ctx, ubd))
|
|
|
|
// slash validator for the first time
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 12})
|
|
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
|
|
oldBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
|
|
_, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 10, 10, fraction)
|
|
assert.NilError(t, err)
|
|
|
|
// end block
|
|
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, 1)
|
|
|
|
// read updating unbonding delegation
|
|
ubd, found = f.stakingKeeper.GetUnbondingDelegation(f.ctx, addrDels[0], addrVals[0])
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(ubd.Entries) == 1)
|
|
|
|
// balance decreased
|
|
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 2), ubd.Entries[0].Balance)
|
|
|
|
// bonded tokens burned
|
|
newBondedPoolBalances := f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
diffTokens := oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
|
|
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 3), diffTokens)
|
|
|
|
// read updated validator
|
|
validator, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
|
|
// power decreased by 3 - 6 stake originally bonded at the time of infraction
|
|
// was still bonded at the time of discovery and was slashed by half, 4 stake
|
|
// bonded at the time of discovery hadn't been bonded at the time of infraction
|
|
// and wasn't slashed
|
|
assert.Equal(t, int64(7), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
|
|
|
|
// slash validator again
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 13})
|
|
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 9, 10, fraction)
|
|
assert.NilError(t, err)
|
|
|
|
ubd, found = f.stakingKeeper.GetUnbondingDelegation(f.ctx, addrDels[0], addrVals[0])
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(ubd.Entries) == 1)
|
|
|
|
// balance decreased again
|
|
assert.DeepEqual(t, math.NewInt(0), ubd.Entries[0].Balance)
|
|
|
|
// bonded tokens burned again
|
|
newBondedPoolBalances = f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
|
|
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 6), diffTokens)
|
|
|
|
// read updated validator
|
|
validator, found = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
|
|
// power decreased by 3 again
|
|
assert.Equal(t, int64(4), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
|
|
|
|
// slash validator again
|
|
// all originally bonded stake has been slashed, so this will have no effect
|
|
// on the unbonding delegation, but it will slash stake bonded since the infraction
|
|
// this may not be the desirable behavior, ref https://github.com/cosmos/cosmos-sdk/issues/1440
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 13})
|
|
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 9, 10, fraction)
|
|
assert.NilError(t, err)
|
|
|
|
ubd, found = f.stakingKeeper.GetUnbondingDelegation(f.ctx, addrDels[0], addrVals[0])
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(ubd.Entries) == 1)
|
|
|
|
// balance unchanged
|
|
assert.DeepEqual(t, math.NewInt(0), ubd.Entries[0].Balance)
|
|
|
|
// bonded tokens burned again
|
|
newBondedPoolBalances = f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
|
|
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 9), diffTokens)
|
|
|
|
// read updated validator
|
|
validator, found = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
|
|
// power decreased by 3 again
|
|
assert.Equal(t, int64(1), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
|
|
|
|
// slash validator again
|
|
// all originally bonded stake has been slashed, so this will have no effect
|
|
// on the unbonding delegation, but it will slash stake bonded since the infraction
|
|
// this may not be the desirable behavior, ref https://github.com/cosmos/cosmos-sdk/issues/1440
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 13})
|
|
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 9, 10, fraction)
|
|
assert.NilError(t, err)
|
|
|
|
ubd, found = f.stakingKeeper.GetUnbondingDelegation(f.ctx, addrDels[0], addrVals[0])
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(ubd.Entries) == 1)
|
|
|
|
// balance unchanged
|
|
assert.DeepEqual(t, math.NewInt(0), ubd.Entries[0].Balance)
|
|
|
|
// just 1 bonded token burned again since that's all the validator now has
|
|
newBondedPoolBalances = f.bankKeeper.GetAllBalances(f.ctx, bondedPool.GetAddress())
|
|
diffTokens = oldBondedPoolBalances.Sub(newBondedPoolBalances...).AmountOf(bondDenom)
|
|
assert.DeepEqual(t, f.stakingKeeper.TokensFromConsensusPower(f.ctx, 10), diffTokens)
|
|
|
|
// apply TM updates
|
|
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, -1)
|
|
|
|
// read updated validator
|
|
// power decreased by 1 again, validator is out of stake
|
|
// validator should be in unbonding period
|
|
validator, _ = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Equal(t, validator.GetStatus(), sdk.Unbonding)
|
|
}
|
|
|
|
// tests Slash at a previous height with a redelegation
|
|
func TestSlashWithRedelegation(t *testing.T) {
|
|
f, addrDels, addrVals := bootstrapSlashTest(t, 10)
|
|
consAddr := sdk.ConsAddress(PKs[0].Address())
|
|
fraction := math.LegacyNewDecWithPrec(5, 1)
|
|
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
|
|
assert.NilError(t, err)
|
|
|
|
// set a redelegation
|
|
rdTokens := f.stakingKeeper.TokensFromConsensusPower(f.ctx, 6)
|
|
rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, time.Unix(0, 0), rdTokens, math.LegacyNewDecFromInt(rdTokens), address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
|
|
assert.NilError(t, f.stakingKeeper.SetRedelegation(f.ctx, rd))
|
|
|
|
// set the associated delegation
|
|
del := types.NewDelegation(addrDels[0].String(), addrVals[1].String(), math.LegacyNewDecFromInt(rdTokens))
|
|
assert.NilError(t, f.stakingKeeper.SetDelegation(f.ctx, del))
|
|
|
|
// update bonded tokens
|
|
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
|
|
notBondedPool := f.stakingKeeper.GetNotBondedPool(f.ctx)
|
|
rdCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, rdTokens.MulRaw(2)))
|
|
|
|
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, bondedPool.GetName(), rdCoins))
|
|
|
|
f.accountKeeper.SetModuleAccount(f.ctx, bondedPool)
|
|
|
|
oldBonded := f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
oldNotBonded := f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
|
|
|
|
// slash validator
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 12})
|
|
_, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
|
|
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 10, 10, fraction)
|
|
assert.NilError(t, err)
|
|
|
|
burnAmount := math.LegacyNewDecFromInt(f.stakingKeeper.TokensFromConsensusPower(f.ctx, 10)).Mul(fraction).TruncateInt()
|
|
|
|
bondedPool = f.stakingKeeper.GetBondedPool(f.ctx)
|
|
notBondedPool = f.stakingKeeper.GetNotBondedPool(f.ctx)
|
|
|
|
// burn bonded tokens from only from delegations
|
|
bondedPoolBalance := f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance))
|
|
|
|
notBondedPoolBalance := f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldNotBonded, notBondedPoolBalance))
|
|
oldBonded = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
|
|
// read updating redelegation
|
|
rd, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(rd.Entries) == 1)
|
|
// read updated validator
|
|
validator, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
// power decreased by 2 - 4 stake originally bonded at the time of infraction
|
|
// was still bonded at the time of discovery and was slashed by half, 4 stake
|
|
// bonded at the time of discovery hadn't been bonded at the time of infraction
|
|
// and wasn't slashed
|
|
assert.Equal(t, int64(8), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
|
|
|
|
// slash the validator again
|
|
_, found = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
|
|
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 10, 10, math.LegacyOneDec())
|
|
assert.NilError(t, err)
|
|
burnAmount = f.stakingKeeper.TokensFromConsensusPower(f.ctx, 7)
|
|
|
|
// read updated pool
|
|
bondedPool = f.stakingKeeper.GetBondedPool(f.ctx)
|
|
notBondedPool = f.stakingKeeper.GetNotBondedPool(f.ctx)
|
|
|
|
// seven bonded tokens burned
|
|
bondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance))
|
|
assert.Assert(math.IntEq(t, oldNotBonded, notBondedPoolBalance))
|
|
|
|
bondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance))
|
|
|
|
notBondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldNotBonded, notBondedPoolBalance))
|
|
oldBonded = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
|
|
// read updating redelegation
|
|
rd, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(rd.Entries) == 1)
|
|
// read updated validator
|
|
validator, found = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
// power decreased by 4
|
|
assert.Equal(t, int64(4), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
|
|
|
|
// slash the validator again, by 100%
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 12})
|
|
_, found = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Assert(t, found)
|
|
|
|
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 10, 10, math.LegacyOneDec())
|
|
assert.NilError(t, err)
|
|
|
|
burnAmount = math.LegacyNewDecFromInt(f.stakingKeeper.TokensFromConsensusPower(f.ctx, 10)).Mul(math.LegacyOneDec()).TruncateInt()
|
|
burnAmount = burnAmount.Sub(math.LegacyOneDec().MulInt(rdTokens).TruncateInt())
|
|
|
|
// read updated pool
|
|
bondedPool = f.stakingKeeper.GetBondedPool(f.ctx)
|
|
notBondedPool = f.stakingKeeper.GetNotBondedPool(f.ctx)
|
|
|
|
bondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldBonded.Sub(burnAmount), bondedPoolBalance))
|
|
notBondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldNotBonded, notBondedPoolBalance))
|
|
oldBonded = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
|
|
// read updating redelegation
|
|
rd, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(rd.Entries) == 1)
|
|
// apply TM updates
|
|
applyValidatorSetUpdates(t, f.ctx, f.stakingKeeper, -1)
|
|
// read updated validator
|
|
// validator decreased to zero power, should be in unbonding period
|
|
validator, _ = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Equal(t, validator.GetStatus(), sdk.Unbonding)
|
|
|
|
// slash the validator again, by 100%
|
|
// no stake remains to be slashed
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 12})
|
|
// validator still in unbonding period
|
|
validator, _ = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Equal(t, validator.GetStatus(), sdk.Unbonding)
|
|
|
|
_, err = f.stakingKeeper.Slash(f.ctx, consAddr, 10, 10, math.LegacyOneDec())
|
|
assert.NilError(t, err)
|
|
|
|
// read updated pool
|
|
bondedPool = f.stakingKeeper.GetBondedPool(f.ctx)
|
|
notBondedPool = f.stakingKeeper.GetNotBondedPool(f.ctx)
|
|
|
|
bondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldBonded, bondedPoolBalance))
|
|
notBondedPoolBalance = f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldNotBonded, notBondedPoolBalance))
|
|
|
|
// read updating redelegation
|
|
rd, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(rd.Entries) == 1)
|
|
// read updated validator
|
|
// power still zero, still in unbonding period
|
|
validator, _ = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, consAddr)
|
|
assert.Equal(t, validator.GetStatus(), sdk.Unbonding)
|
|
}
|
|
|
|
// tests Slash at a previous height with both an unbonding delegation and a redelegation
|
|
func TestSlashBoth(t *testing.T) {
|
|
f, addrDels, addrVals := bootstrapSlashTest(t, 10)
|
|
fraction := math.LegacyNewDecWithPrec(5, 1)
|
|
bondDenom, err := f.stakingKeeper.BondDenom(f.ctx)
|
|
assert.NilError(t, err)
|
|
|
|
// set a redelegation with expiration timestamp beyond which the
|
|
// redelegation shouldn't be slashed
|
|
rdATokens := f.stakingKeeper.TokensFromConsensusPower(f.ctx, 6)
|
|
rdA := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, time.Unix(0, 0), rdATokens, math.LegacyNewDecFromInt(rdATokens), address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
|
|
assert.NilError(t, f.stakingKeeper.SetRedelegation(f.ctx, rdA))
|
|
|
|
// set the associated delegation
|
|
delA := types.NewDelegation(addrDels[0].String(), addrVals[1].String(), math.LegacyNewDecFromInt(rdATokens))
|
|
assert.NilError(t, f.stakingKeeper.SetDelegation(f.ctx, delA))
|
|
|
|
// set an unbonding delegation with expiration timestamp (beyond which the
|
|
// unbonding delegation shouldn't be slashed)
|
|
ubdATokens := f.stakingKeeper.TokensFromConsensusPower(f.ctx, 4)
|
|
ubdA := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11,
|
|
time.Unix(0, 0), ubdATokens, address.NewBech32Codec("cosmosvaloper"), address.NewBech32Codec("cosmos"))
|
|
assert.NilError(t, f.stakingKeeper.SetUnbondingDelegation(f.ctx, ubdA))
|
|
|
|
bondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, rdATokens.MulRaw(2)))
|
|
notBondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, ubdATokens))
|
|
|
|
// update bonded tokens
|
|
bondedPool := f.stakingKeeper.GetBondedPool(f.ctx)
|
|
notBondedPool := f.stakingKeeper.GetNotBondedPool(f.ctx)
|
|
|
|
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, bondedPool.GetName(), bondedCoins))
|
|
assert.NilError(t, banktestutil.FundModuleAccount(f.ctx, f.bankKeeper, notBondedPool.GetName(), notBondedCoins))
|
|
|
|
f.accountKeeper.SetModuleAccount(f.ctx, bondedPool)
|
|
f.accountKeeper.SetModuleAccount(f.ctx, notBondedPool)
|
|
|
|
oldBonded := f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
oldNotBonded := f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
|
|
// slash validator
|
|
f.ctx = integration.SetHeaderInfo(f.ctx, header.Info{Height: 12})
|
|
_, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, sdk.GetConsAddress(PKs[0]))
|
|
assert.Assert(t, found)
|
|
consAddr0 := sdk.ConsAddress(PKs[0].Address())
|
|
_, err = f.stakingKeeper.Slash(f.ctx, consAddr0, 10, 10, fraction)
|
|
assert.NilError(t, err)
|
|
|
|
burnedNotBondedAmount := fraction.MulInt(ubdATokens).TruncateInt()
|
|
burnedBondAmount := math.LegacyNewDecFromInt(f.stakingKeeper.TokensFromConsensusPower(f.ctx, 10)).Mul(fraction).TruncateInt()
|
|
burnedBondAmount = burnedBondAmount.Sub(burnedNotBondedAmount)
|
|
|
|
// read updated pool
|
|
bondedPool = f.stakingKeeper.GetBondedPool(f.ctx)
|
|
notBondedPool = f.stakingKeeper.GetNotBondedPool(f.ctx)
|
|
|
|
bondedPoolBalance := f.bankKeeper.GetBalance(f.ctx, bondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldBonded.Sub(burnedBondAmount), bondedPoolBalance))
|
|
|
|
notBondedPoolBalance := f.bankKeeper.GetBalance(f.ctx, notBondedPool.GetAddress(), bondDenom).Amount
|
|
assert.Assert(math.IntEq(t, oldNotBonded.Sub(burnedNotBondedAmount), notBondedPoolBalance))
|
|
|
|
// read updating redelegation
|
|
rdA, found = f.stakingKeeper.Redelegations.Get(f.ctx, collections.Join3(addrDels[0].Bytes(), addrVals[0].Bytes(), addrVals[1].Bytes()))
|
|
assert.Assert(t, found)
|
|
assert.Assert(t, len(rdA.Entries) == 1)
|
|
// read updated validator
|
|
validator, found := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, sdk.GetConsAddress(PKs[0]))
|
|
assert.Assert(t, found)
|
|
// power not decreased, all stake was bonded since
|
|
assert.Equal(t, int64(10), validator.GetConsensusPower(f.stakingKeeper.PowerReduction(f.ctx)))
|
|
}
|
|
|
|
func TestSlashAmount(t *testing.T) {
|
|
f, _, _ := bootstrapSlashTest(t, 10)
|
|
consAddr := sdk.ConsAddress(PKs[0].Address())
|
|
fraction := math.LegacyNewDecWithPrec(5, 1)
|
|
burnedCoins, err := f.stakingKeeper.Slash(f.ctx, consAddr, int64(f.app.LastBlockHeight()), 10, fraction)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, burnedCoins.GT(math.ZeroInt()))
|
|
|
|
// test the case where the validator was not found, which should return no coins
|
|
_, addrVals := generateAddresses(f, 100)
|
|
noBurned, err := f.stakingKeeper.Slash(f.ctx, sdk.ConsAddress(addrVals[0]), int64(f.app.LastBlockHeight())+1, 10, fraction)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, math.NewInt(0).Equal(noBurned))
|
|
}
|
|
|
|
// TestFixAvoidFullSlashPenalty fixes the following issue: https://github.com/cosmos/cosmos-sdk/issues/20641
|
|
func TestFixAvoidFullSlashPenalty(t *testing.T) {
|
|
// setup
|
|
f := initFixture(t, false)
|
|
ctx := f.ctx
|
|
|
|
stakingMsgServer := keeper.NewMsgServerImpl(f.stakingKeeper)
|
|
bondDenom, err := f.stakingKeeper.BondDenom(ctx)
|
|
require.NoError(t, err)
|
|
// create 2 evil validators, controlled by attacker
|
|
evilValPubKey := secp256k1.GenPrivKey().PubKey()
|
|
evilValPubKey2 := secp256k1.GenPrivKey().PubKey()
|
|
// attacker user account
|
|
badtestAcc := sdk.AccAddress("addr1_______________")
|
|
// normal users who stakes on evilValAddr1
|
|
testAcc1 := sdk.AccAddress("addr2_______________")
|
|
testAcc2 := sdk.AccAddress("addr3_______________")
|
|
createAccount(t, ctx, f.accountKeeper, badtestAcc)
|
|
createAccount(t, ctx, f.accountKeeper, testAcc1)
|
|
createAccount(t, ctx, f.accountKeeper, testAcc2)
|
|
// fund all accounts
|
|
testCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, f.stakingKeeper.TokensFromConsensusPower(ctx, 1)))
|
|
require.NoError(t, banktestutil.FundAccount(ctx, f.bankKeeper, badtestAcc, testCoins))
|
|
require.NoError(t, banktestutil.FundAccount(ctx, f.bankKeeper, testAcc1, testCoins))
|
|
require.NoError(t, banktestutil.FundAccount(ctx, f.bankKeeper, testAcc2, testCoins))
|
|
// create evilValAddr1 for normal staking operations
|
|
evilValAddr1 := sdk.ValAddress(evilValPubKey.Address())
|
|
createAccount(t, ctx, f.accountKeeper, evilValAddr1.Bytes())
|
|
require.NoError(t, banktestutil.FundAccount(ctx, f.bankKeeper, sdk.AccAddress(evilValAddr1), testCoins))
|
|
createValMsg1, _ := types.NewMsgCreateValidator(
|
|
evilValAddr1.String(), evilValPubKey, testCoins[0], types.Description{Details: "test"}, types.NewCommissionRates(math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDec(0)), math.OneInt())
|
|
_, err = stakingMsgServer.CreateValidator(ctx, createValMsg1)
|
|
require.NoError(t, err)
|
|
// very small amount coin for evilValAddr2
|
|
smallCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, math.NewInt(1)))
|
|
// create evilValAddr2 to circumvent slashing
|
|
evilValAddr2 := sdk.ValAddress(evilValPubKey2.Address())
|
|
require.NoError(t, banktestutil.FundAccount(ctx, f.bankKeeper, sdk.AccAddress(evilValAddr2), smallCoins))
|
|
createValMsg3, _ := types.NewMsgCreateValidator(
|
|
evilValAddr2.String(), evilValPubKey2, smallCoins[0], types.Description{Details: "test"}, types.NewCommissionRates(math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDec(0)), math.OneInt())
|
|
createAccount(t, ctx, f.accountKeeper, evilValAddr2.Bytes())
|
|
_, err = stakingMsgServer.CreateValidator(ctx, createValMsg3)
|
|
require.NoError(t, err)
|
|
// next block
|
|
ctx = integration.SetHeaderInfo(ctx, header.Info{Height: int64(f.app.LastBlockHeight()) + 1})
|
|
_, state := f.app.Deliver(t, ctx, nil)
|
|
_, err = f.app.Commit(state)
|
|
require.NoError(t, err)
|
|
// all accs delegate to evilValAddr1
|
|
delMsg := types.NewMsgDelegate(badtestAcc.String(), evilValAddr1.String(), testCoins[0])
|
|
_, err = stakingMsgServer.Delegate(ctx, delMsg)
|
|
require.NoError(t, err)
|
|
delMsg = types.NewMsgDelegate(testAcc1.String(), evilValAddr1.String(), testCoins[0])
|
|
_, err = stakingMsgServer.Delegate(ctx, delMsg)
|
|
require.NoError(t, err)
|
|
delMsg = types.NewMsgDelegate(testAcc2.String(), evilValAddr1.String(), testCoins[0])
|
|
_, err = stakingMsgServer.Delegate(ctx, delMsg)
|
|
require.NoError(t, err)
|
|
// next block
|
|
_, state = f.app.Deliver(t, ctx, nil)
|
|
_, err = f.app.Commit(state)
|
|
require.NoError(t, err)
|
|
// 1. badtestAcc redelegates from evilValAddr1 to evilValAddr2
|
|
redelMsg := types.NewMsgBeginRedelegate(badtestAcc.String(), evilValAddr1.String(), evilValAddr2.String(), smallCoins[0])
|
|
_, err = stakingMsgServer.BeginRedelegate(ctx, redelMsg)
|
|
require.NoError(t, err)
|
|
// 2. evilValAddr2 undelegates its self-delegation and jail themselves
|
|
undelMsg := types.NewMsgUndelegate(sdk.AccAddress(evilValAddr2).String(), evilValAddr2.String(), smallCoins[0])
|
|
_, err = stakingMsgServer.Undelegate(ctx, undelMsg)
|
|
require.NoError(t, err)
|
|
// assert evilValAddr2 is jailed
|
|
evilVal2, err := f.stakingKeeper.GetValidator(ctx, evilValAddr2)
|
|
require.NoError(t, err)
|
|
require.True(t, evilVal2.Jailed)
|
|
// next block
|
|
_, state = f.app.Deliver(t, ctx, nil)
|
|
_, err = f.app.Commit(state)
|
|
require.NoError(t, err)
|
|
// evilValAddr1 is bad!
|
|
// lets slash evilValAddr1 with a 100% penalty
|
|
evilVal, err := f.stakingKeeper.GetValidator(ctx, evilValAddr1)
|
|
require.NoError(t, err)
|
|
evilValConsAddr, err := evilVal.GetConsAddr()
|
|
require.NoError(t, err)
|
|
evilPower := f.stakingKeeper.TokensToConsensusPower(ctx, evilVal.Tokens)
|
|
err = f.slashKeeper.Slash(ctx, evilValConsAddr, math.LegacyMustNewDecFromStr("1.0"), evilPower, 3)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func createAccount(t *testing.T, ctx context.Context, k authkeeper.AccountKeeperI, addr sdk.AccAddress) {
|
|
t.Helper()
|
|
acc := k.NewAccountWithAddress(ctx, addr)
|
|
k.SetAccount(ctx, acc)
|
|
}
|