diff --git a/app/config/const.go b/app/config/const.go index 03421c9e..e980963f 100644 --- a/app/config/const.go +++ b/app/config/const.go @@ -221,4 +221,5 @@ const ( Upgrade2_9 = "v2.9" Upgrade2_10 = "v2.10" Upgrade2_11 = "v2.11" + Upgrade2_12 = "v2.12" ) diff --git a/app/custom_queriers/custom_queriers.go b/app/custom_queriers/custom_queriers.go index 5318a798..b5b9e547 100644 --- a/app/custom_queriers/custom_queriers.go +++ b/app/custom_queriers/custom_queriers.go @@ -3,14 +3,16 @@ package custom_queriers import ( "encoding/json" "fmt" + "strings" + "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" alliancebindings "github.com/terra-money/alliance/x/alliance/bindings" alliancekeeper "github.com/terra-money/alliance/x/alliance/keeper" tokenfactorybindings "github.com/terra-money/core/v2/x/tokenfactory/bindings" tokenfactorykeeper "github.com/terra-money/core/v2/x/tokenfactory/keeper" - "strings" + + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" sdk "github.com/cosmos/cosmos-sdk/types" ) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 37cf352a..8d529222 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -306,6 +306,7 @@ func NewTerraAppKeepers( ) keepers.TokenFactoryKeeper = tokenfactorykeeper.NewKeeper( keys[tokenfactorytypes.StoreKey], + maccPerms, keepers.AccountKeeper, &keepers.BankKeeper, keepers.DistrKeeper, diff --git a/app/upgrade_handler.go b/app/upgrade_handler.go index 24aaf2e6..6c3e4837 100644 --- a/app/upgrade_handler.go +++ b/app/upgrade_handler.go @@ -8,6 +8,7 @@ import ( terraappconfig "github.com/terra-money/core/v2/app/config" v2_10 "github.com/terra-money/core/v2/app/upgrades/v2.10" v2_11 "github.com/terra-money/core/v2/app/upgrades/v2.11" + v2_12 "github.com/terra-money/core/v2/app/upgrades/v2.12" v2_2_0 "github.com/terra-money/core/v2/app/upgrades/v2.2.0" v2_3_0 "github.com/terra-money/core/v2/app/upgrades/v2.3.0" v2_4 "github.com/terra-money/core/v2/app/upgrades/v2.4" @@ -111,6 +112,14 @@ func (app *TerraApp) RegisterUpgradeHandlers() { app.Keepers.TransferKeeper, ), ) + app.Keepers.UpgradeKeeper.SetUpgradeHandler( + terraappconfig.Upgrade2_12, + v2_12.CreateUpgradeHandler( + app.GetModuleManager(), + app.GetConfigurator(), + app.Keepers, + ), + ) } func (app *TerraApp) RegisterUpgradeStores() { diff --git a/app/upgrades/v2.11/upgrade.go b/app/upgrades/v2.11/upgrade.go index 15a1de0a..44bfa54d 100644 --- a/app/upgrades/v2.11/upgrade.go +++ b/app/upgrades/v2.11/upgrade.go @@ -30,7 +30,7 @@ func CreateUpgradeHandler( // https://github.com/strangelove-ventures/escrow-checker/commit/adf0d867e2210c9ff0a27d8dff1c74ed0c8a00dc updates := []EscrowUpdate{ { - EscrowAddress: sdk.AccAddress("terra1s308jav50mgct9x4f87u23w2tfe8q6qe45y7s4"), + EscrowAddress: sdk.MustAccAddressFromBech32("terra1s308jav50mgct9x4f87u23w2tfe8q6qe45y7s4"), Assets: []sdk.Coin{sdk.NewCoin("ibc/815FC81EB6BD612206BD9A9909A02F7691D24A5B97CDFE2124B1BDCA9D4AB14C", sdk.NewInt(1000000000))}, }, } diff --git a/app/upgrades/v2.12/upgrade.go b/app/upgrades/v2.12/upgrade.go new file mode 100644 index 00000000..dd089237 --- /dev/null +++ b/app/upgrades/v2.12/upgrade.go @@ -0,0 +1,204 @@ +package v2_12 + +import ( + "fmt" + + "cosmossdk.io/math" + accountkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/terra-money/core/v2/app/keepers" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" +) + +type EscrowUpdate struct { + EscrowAddress sdk.AccAddress + Assets []sdk.Coin +} + +// To test this upgrade handler set the following address "terra1v0eee20gjl68fuk0chyrkch2z7suw2mhg3wkxf" +// on the variables below: addr and multisigAddr. +// then run: npm run test:chain:upgrade:v12 +func CreateUpgradeHandler( + mm *module.Manager, + cfg module.Configurator, + k keepers.TerraAppKeepers, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + + if err := updateValidatorsMinCommissionRate(ctx, k.StakingKeeper); err != nil { + return nil, err + } + + var addr sdk.AccAddress + var multisigAddr sdk.AccAddress + + if ctx.ChainID() == "phoenix-1" { + addr = sdk.MustAccAddressFromBech32("terra1885dgdvn5u8sjfaefvr39arssaxgqmd29ht0aa") + multisigAddr = sdk.MustAccAddressFromBech32("terra19yxffalxzu88n5lnj40trehpryemqsz7pnnwxp8v73hxz0rl2u9q5qqwh4") + } else { + addr = sdk.MustAccAddressFromBech32("terra1wd8tc98um0x6c9l46vhg00gudzgleefl6tvshd") + multisigAddr = sdk.MustAccAddressFromBech32("terra1xduqpf6aah0nftppuez7upl6curmykl3cxdek4h5wacw7hn0fr9sr029ze") + } + + if err := burnTokensFromAccount(ctx, k.StakingKeeper, k.BankKeeper, k.DistrKeeper, k.AccountKeeper, addr); err != nil { + return nil, err + } + if err := burnTokensFromAccount(ctx, k.StakingKeeper, k.BankKeeper, k.DistrKeeper, k.AccountKeeper, multisigAddr); err != nil { + return nil, err + } + + return mm.RunMigrations(ctx, cfg, vm) + } +} + +func updateValidatorsMinCommissionRate(ctx sdk.Context, sk *stakingkeeper.Keeper) error { + // Update min commission rate for new / validators who are updating + stakingParams := sk.GetParams(ctx) + stakingParams.MinCommissionRate = sdk.MustNewDecFromStr("0.05") + if err := sk.SetParams(ctx, stakingParams); err != nil { + return err + } + + // Update all validators to have a min commission rate of 5% + validators := sk.GetAllValidators(ctx) + for _, validator := range validators { + update := false + if validator.Commission.MaxRate.LT(sdk.MustNewDecFromStr("0.05")) { + validator.Commission.MaxRate = sdk.MustNewDecFromStr("0.05") + update = true + } + if validator.Commission.Rate.LT(sdk.MustNewDecFromStr("0.05")) { + // force update without checking the <24h restriction and the max update rate + validator.Commission.Rate = sdk.MustNewDecFromStr("0.05") + update = true + } + if update { + validator.Commission.UpdateTime = ctx.BlockTime() + if err := sk.Hooks().BeforeValidatorModified(ctx, validator.GetOperator()); err != nil { + return err + } + sk.SetValidator(ctx, validator) + } + } + return nil +} + +func burnTokensFromAccount(ctx sdk.Context, sk *stakingkeeper.Keeper, bk bankkeeper.Keeper, dk distributionkeeper.Keeper, ak accountkeeper.AccountKeeper, addr sdk.AccAddress) error { + acc := ak.GetAccount(ctx, addr) + if acc == nil { + return fmt.Errorf("account %s not found", addr) + } + // Iterate delegations and unbond all shares + // burning the coins immediately + bondDenom := sk.GetParams(ctx).BondDenom + var err error + sk.IterateDelegatorDelegations(ctx, addr, func(d stakingtypes.Delegation) (stop bool) { + var valAddr sdk.ValAddress + valAddr, err = sdk.ValAddressFromBech32(d.ValidatorAddress) + if err != nil { + return true + } + + // Withdraw delegation rewards first + _, err = dk.WithdrawDelegationRewards(ctx, addr, valAddr) + if err != nil { + return true + } + // Use this method without adding unbonding to the unbondings queue + // because it's not necessary to wait for the unbonding period + var unbondedAmount math.Int + unbondedAmount, err = sk.Unbond(ctx, addr, valAddr, d.Shares) + if err != nil { + return true + } + + // After unbonding, burn the coins depending on the validator's status + validator := sk.Validator(ctx, valAddr) + if validator.IsBonded() { + if err = bk.BurnCoins(ctx, stakingtypes.BondedPoolName, sdk.NewCoins(sdk.NewCoin(bondDenom, unbondedAmount))); err != nil { + return true + } + } else { + if err = bk.BurnCoins(ctx, stakingtypes.NotBondedPoolName, sdk.NewCoins(sdk.NewCoin(bondDenom, unbondedAmount))); err != nil { + return true + } + } + + return false + }) + if err != nil { + return err + } + + // Given one of the states can be undelegating, we need to iterate over all unbonding delegations + // and remove them manually to ensure that the undelegated coins are burned. + sk.IterateDelegatorUnbondingDelegations(ctx, addr, func(ubd stakingtypes.UnbondingDelegation) (stop bool) { + for i := 0; i < len(ubd.Entries); i++ { + entry := ubd.Entries[i] + ubd.RemoveEntry(int64(i)) + i-- + sk.DeleteUnbondingIndex(ctx, entry.UnbondingId) + + // track undelegation only when remaining or truncated shares are non-zero + if !entry.Balance.IsZero() { + amt := sdk.NewCoin(bondDenom, entry.Balance) + if err = bk.BurnCoins( + ctx, stakingtypes.NotBondedPoolName, sdk.NewCoins(amt), + ); err != nil { + return true + } + } + } + sk.RemoveUnbondingDelegation(ctx, ubd) + return false + }) + if err != nil { + return err + } + + // Redelegations are two queues but no coins are custodied in any "redelegations_pool", + // so we can just iterate over all redelegations and remove the indices to prevent issues. + sk.IterateDelegatorRedelegations(ctx, addr, func(red stakingtypes.Redelegation) (stop bool) { + for i := 0; i < len(red.Entries); i++ { + entry := red.Entries[i] + red.RemoveEntry(int64(i)) + i-- + sk.DeleteUnbondingIndex(ctx, entry.UnbondingId) + } + sk.RemoveRedelegation(ctx, red) + return false + }) + + // Set account back to a base account before burning to vest everything + switch vestingAcc := acc.(type) { + case *types.ContinuousVestingAccount: + ak.SetAccount(ctx, vestingAcc.BaseVestingAccount) + case *types.DelayedVestingAccount: + ak.SetAccount(ctx, vestingAcc.BaseVestingAccount) + case *types.PeriodicVestingAccount: + ak.SetAccount(ctx, vestingAcc.BaseVestingAccount) + default: + // do nothing + } + + // Burn all coins in the addr + bk.IterateAccountBalances(ctx, addr, func(balance sdk.Coin) bool { + err = bk.SendCoinsFromAccountToModule(ctx, addr, stakingtypes.NotBondedPoolName, sdk.NewCoins(balance)) + if err != nil { + return true + } + err = bk.BurnCoins(ctx, stakingtypes.NotBondedPoolName, sdk.NewCoins(balance)) + if err != nil { + return true + } + return false + }) + return err +} diff --git a/go.mod b/go.mod index 6095e9c1..46998513 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( cosmossdk.io/simapp v0.0.0-20230608160436-666c345ad23d cosmossdk.io/tools/rosetta v0.2.1 github.com/CosmWasm/wasmd v0.45.0 - github.com/CosmWasm/wasmvm v1.5.2 + github.com/CosmWasm/wasmvm v1.5.3 github.com/cometbft/cometbft v0.37.4 github.com/cometbft/cometbft-db v0.8.0 github.com/cosmos/cosmos-proto v1.0.0-beta.4 @@ -18,7 +18,7 @@ require ( github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3-0.20240228213828-cce7f56d000b github.com/cosmos/ibc-apps/modules/async-icq/v7 v7.0.0 github.com/cosmos/ibc-apps/modules/ibc-hooks/v7 v7.0.0-20230803181732-7c8f814d3b79 - github.com/cosmos/ibc-go/v7 v7.3.1 + github.com/cosmos/ibc-go/v7 v7.4.1 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 github.com/gorilla/mux v1.8.0 @@ -192,7 +192,6 @@ require ( replace ( github.com/cosmos/cosmos-sdk => github.com/terra-money/cosmos-sdk v0.47.10-terra.0 - github.com/cosmos/ibc-go/v7 => github.com/terra-money/ibc-go/v7 v7.3.1-terra.0 github.com/cosmos/ledger-cosmos-go => github.com/terra-money/ledger-terra-go v0.11.2 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 diff --git a/go.sum b/go.sum index 078fcbde..dc62f620 100644 --- a/go.sum +++ b/go.sum @@ -227,8 +227,8 @@ github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRr github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= github.com/CosmWasm/wasmd v0.45.0 h1:9zBqrturKJwC2kVsfHvbrA++EN0PS7UTXCffCGbg6JI= github.com/CosmWasm/wasmd v0.45.0/go.mod h1:RnSAiqbNIZu4QhO+0pd7qGZgnYAMBPGmXpzTADag944= -github.com/CosmWasm/wasmvm v1.5.2 h1:+pKB1Mz9GZVt1vadxB+EDdD1FOz3dMNjIKq/58/lrag= -github.com/CosmWasm/wasmvm v1.5.2/go.mod h1:Q0bSEtlktzh7W2hhEaifrFp1Erx11ckQZmjq8FLCyys= +github.com/CosmWasm/wasmvm v1.5.3 h1:wcmkey/WkTGwCTHGBD+fRS3cbhhipR9q34kbCZUQSsc= +github.com/CosmWasm/wasmvm v1.5.3/go.mod h1:Q0bSEtlktzh7W2hhEaifrFp1Erx11ckQZmjq8FLCyys= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -408,6 +408,8 @@ github.com/cosmos/ibc-apps/modules/async-icq/v7 v7.0.0 h1:mMHedP3Q+mz5gpOWNz0P+X github.com/cosmos/ibc-apps/modules/async-icq/v7 v7.0.0/go.mod h1:/P6l2bWo2AR3rrsfs0DHuFZO3Imzb93sxFD3ihrIgw4= github.com/cosmos/ibc-apps/modules/ibc-hooks/v7 v7.0.0-20230803181732-7c8f814d3b79 h1:pCxyhIxgWTabAQC5UerkITraHG3SwajdLKKMCFDWCv4= github.com/cosmos/ibc-apps/modules/ibc-hooks/v7 v7.0.0-20230803181732-7c8f814d3b79/go.mod h1:JwHFbo1oX/ht4fPpnPvmhZr+dCkYK1Vihw+vZE9umR4= +github.com/cosmos/ibc-go/v7 v7.4.1 h1:95hR5Mdgk2/Z6Ynsq537BOU8LJAOsHR5g0N0ffhFLYg= +github.com/cosmos/ibc-go/v7 v7.4.1/go.mod h1:L/KaEhzV5TGUCTfGysVgMBQtl5Dm7hHitfpk+GIeoAo= github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= @@ -1138,8 +1140,6 @@ github.com/terra-money/alliance v0.3.6 h1:FWfix+mKcCrXvdk29MgfXGj0JThOsBxzK81OiS github.com/terra-money/alliance v0.3.6/go.mod h1:gyenuDQEwyN6mfiOEkaRBaokgk9ryBeU3eCAiZpVKZg= github.com/terra-money/cosmos-sdk v0.47.10-terra.0 h1:vpod9zXeBp8S8JfP0++YzwCvCEMkJnz3qRmz0pciEQI= github.com/terra-money/cosmos-sdk v0.47.10-terra.0/go.mod h1:UWpgWkhcsBIATS68uUC0del7IiBN4hPv/vqg8Zz23uw= -github.com/terra-money/ibc-go/v7 v7.3.1-terra.0 h1:CF+iicqyI4BJsW2zjUrUrTxRRrPWFZC30VqvlRyVl28= -github.com/terra-money/ibc-go/v7 v7.3.1-terra.0/go.mod h1:wvx4pPBofe5ZdMNV3OFRxSI4auEP5Qfqf8JXLLNV04g= github.com/terra-money/ledger-terra-go v0.11.2 h1:BVXZl+OhJOri6vFNjjVaTabRLApw9MuG7mxWL4V718c= github.com/terra-money/ledger-terra-go v0.11.2/go.mod h1:ClJ2XMj1ptcnONzKH+GhVPi7Y8pXIT+UzJ0TNt0tfZE= github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= diff --git a/integration-tests/package.json b/integration-tests/package.json index 05ad01bb..974860cc 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -7,6 +7,7 @@ "test:init" : "bash src/setup/init-test-framework.sh", "test:relayer": "bash src/setup/relayer/init-relayer.sh", "test:chain:upgrade" : "bash src/setup/chain-upgrade/chain-upgrade.sh", + "test:chain:upgrade:v12" : "bash src/setup/chain-upgrade/upgrade-simulation-12.sh", "test:start" : "jest --runInBand --detectOpenHandles", "start": "npm run test:init && npm run test:relayer && npm run test:start", "test:clean": "rm -rf src/test-data chain-upgrade-data && pkill terrad && pkill terrad && pkill relayer" diff --git a/integration-tests/src/setup/chain-upgrade/chain-upgrade.sh b/integration-tests/src/setup/chain-upgrade/chain-upgrade.sh index 9bce11ec..7f4396ba 100755 --- a/integration-tests/src/setup/chain-upgrade/chain-upgrade.sh +++ b/integration-tests/src/setup/chain-upgrade/chain-upgrade.sh @@ -42,10 +42,10 @@ fi # init genesis $OLD_BINARY init test --home $CHAIN_HOME --chain-id=$CHAIN_ID echo $VAL_MNEMONIC_1 | $OLD_BINARY keys add val1 --home $CHAIN_HOME --recover --keyring-backend=test -VAL_ADDR_1=$($OLD_BINARY keys list emi --output=json | jq .[0].address -r) +VAL_ADDR_1=$($OLD_BINARY keys list val1 --output=json | jq .[0].address -r) echo $WALLET_MNEMONIC_1 | $OLD_BINARY keys add wallet1 --home $CHAIN_HOME --recover --keyring-backend=test -WALLET_ADDR_1=$($OLD_BINARY keys list emi --output=json | jq .[0].address -r) +WALLET_ADDR_1=$($OLD_BINARY keys list wallet1 --output=json | jq .[0].address -r) $OLD_BINARY genesis add-genesis-account $($OLD_BINARY --home $CHAIN_HOME keys show val1 --keyring-backend test -a) 100000000000uluna --home $CHAIN_HOME $OLD_BINARY genesis gentx val1 1000000000uluna --home $CHAIN_HOME --chain-id $CHAIN_ID --keyring-backend test diff --git a/integration-tests/src/setup/chain-upgrade/upgrade-simulation-12.sh b/integration-tests/src/setup/chain-upgrade/upgrade-simulation-12.sh new file mode 100755 index 00000000..6dacca5a --- /dev/null +++ b/integration-tests/src/setup/chain-upgrade/upgrade-simulation-12.sh @@ -0,0 +1,188 @@ +#!/bin/bash + +OLD_VERSION=release/v2.11 +UPGRADE_HEIGHT=35 +CHAIN_ID=phoenix-1 +CHAIN_HOME=$(pwd)/chain-upgrade-data +DENOM=uluna +SOFTWARE_UPGRADE_NAME="v2.12" +GOV_PERIOD="3s" + +VAL_MNEMONIC_1="clock post desk civil pottery foster expand merit dash seminar song memory figure uniform spice circle try happy obvious trash crime hybrid hood cushion" +VAL_MNEMONIC_2="alley afraid soup fall idea toss can goose become valve initial strong forward bright dish figure check leopard decide warfare hub unusual join cart" +WALLET_MNEMONIC_1="banner spread envelope side kite person disagree path silver will brother under couch edit food venture squirrel civil budget number acquire point work mass" + +export OLD_BINARY=$CHAIN_HOME/terrad_old +export NEW_BINARY=$CHAIN_HOME/terrad_new + +rm -rf /tmp/terra +rm -r $CHAIN_HOME +mkdir $CHAIN_HOME +killall terrad_old +killall terrad_new + +# install old binary +if ! command -v $OLD_BINARY &> /dev/null +then + mkdir -p /tmp/terra + cd /tmp/terra + git clone https://github.com/terra-money/core + cd core + git checkout $OLD_VERSION + make build + cp /tmp/terra/core/build/terrad $CHAIN_HOME/terrad_old + cd $CHAIN_HOME +fi + +# install new binary +if ! command -v $NEW_BINARY &> /dev/null +then + cd ../.. + make build + cp build/terrad $NEW_BINARY +fi + +# init genesis +$OLD_BINARY init test --home $CHAIN_HOME --chain-id=$CHAIN_ID +echo $VAL_MNEMONIC_1 | $OLD_BINARY keys add val1 --home $CHAIN_HOME --recover --keyring-backend=test +VAL_ADDR_1=$($OLD_BINARY keys show val1 --home $CHAIN_HOME --keyring-backend=test --output=json | jq .address -r) + +echo $WALLET_MNEMONIC_1 | $OLD_BINARY keys add wallet1 --home $CHAIN_HOME --recover --keyring-backend=test +WALLET_ADDR_1=$($OLD_BINARY keys show wallet1 --home $CHAIN_HOME --keyring-backend=test --output=json | jq .address -r) + +$OLD_BINARY genesis add-genesis-account $($OLD_BINARY --home $CHAIN_HOME keys show val1 --keyring-backend test -a) 100000000000uluna --home $CHAIN_HOME + +CURRENT_TIME=$(date +%s) +echo "Current time: $CURRENT_TIME" +$OLD_BINARY genesis add-genesis-account $($OLD_BINARY --home $CHAIN_HOME keys show wallet1 --keyring-backend test -a) 100000000000uluna --vesting-amount 200000000uluna --vesting-start-time $CURRENT_TIME --vesting-end-time $(($CURRENT_TIME + 10000)) --home $CHAIN_HOME + +$OLD_BINARY genesis gentx val1 1000000000uluna --home $CHAIN_HOME --chain-id $CHAIN_ID --keyring-backend test --commission-max-rate 0.01 --commission-rate 0.01 --commission-max-change-rate 0.01 +$OLD_BINARY genesis collect-gentxs --home $CHAIN_HOME + +sed -i -e "s/\"max_deposit_period\": \"172800s\"/\"max_deposit_period\": \"$GOV_PERIOD\"/g" $CHAIN_HOME/config/genesis.json +sed -i -e "s/\"voting_period\": \"172800s\"/\"voting_period\": \"$GOV_PERIOD\"/g" $CHAIN_HOME/config/genesis.json + +sed -i -e 's/timeout_commit = "5s"/timeout_commit = "1s"/g' $CHAIN_HOME/config/config.toml +sed -i -e 's/timeout_propose = "3s"/timeout_propose = "1s"/g' $CHAIN_HOME/config/config.toml +sed -i -e 's/index_all_keys = false/index_all_keys = true/g' $CHAIN_HOME/config/config.toml +sed -i -e 's/enable = false/enable = true/g' $CHAIN_HOME/config/app.toml +sed -i -e 's/swagger = false/swagger = true/g' $CHAIN_HOME/config/app.toml + +# run old node +echo "Starting old binary on a separate process" +if [[ "$OSTYPE" == "darwin"* ]]; then + screen -L -dmS node1 $OLD_BINARY start --log_level trace --log_format json --home $CHAIN_HOME --pruning=nothing +else + screen -L -Logfile $CHAIN_HOME/log-screen.log -dmS node1 $OLD_BINARY start --log_level trace --log_format json --home $CHAIN_HOME --pruning=nothing +fi + +sleep 15 + +VALOPER_ADDR_1=$($OLD_BINARY q staking validators --output=json --home $CHAIN_HOME | jq .validators[0].operator_address -r) + +# Stake and assert it is staked +echo "Delegate" +NO_ECHO=$($OLD_BINARY tx staking delegate $VALOPER_ADDR_1 100000000uluna --keyring-backend test --chain-id $CHAIN_ID --home $CHAIN_HOME --from wallet1 -y) +sleep 2 +DELEGATIONS=$($OLD_BINARY query staking delegations $WALLET_ADDR_1 --home $CHAIN_HOME --output=json | jq ".delegation_responses | length") +if [[ "$DELEGATIONS" == "0" ]]; then + echo "Delegation failed" + exit 1 +fi + +# Unbond and assert the unbonding delegation +echo "Unbond" +NO_ECHO=$($OLD_BINARY tx staking unbond $VALOPER_ADDR_1 1000000uluna --keyring-backend test --chain-id $CHAIN_ID --home $CHAIN_HOME --from wallet1 -y) +sleep 2 +UNBONDINGS=$($OLD_BINARY query staking unbonding-delegations $WALLET_ADDR_1 --home $CHAIN_HOME --output=json | jq ".unbonding_responses | length" ) +if [[ "$UNBONDINGS" == "0" ]]; then + echo "Unbonding failed" + exit 1 +fi + +GOV_ADDRESS=$($OLD_BINARY query auth module-account gov --home $CHAIN_HOME --output json | jq .account.base_account.address -r) +echo '{ + "messages": [ + { + "@type": "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade", + "authority" : "'"$GOV_ADDRESS"'", + "plan" : { + "name": "'"$SOFTWARE_UPGRADE_NAME"'", + "time": "0001-01-01T00:00:00Z", + "height": "'"$UPGRADE_HEIGHT"'", + "upgraded_client_state": null + } + } + ], + "metadata": "", + "deposit": "550000000'$DENOM'", + "title": "Upgrade to '$SOFTWARE_UPGRADE_NAME'", + "summary": "Source Code Version https://github.com/terra-money/core" +}' > $CHAIN_HOME/software-upgrade.json + +echo "Submit proposal" +NO_ECHO=$($OLD_BINARY tx gov submit-proposal $CHAIN_HOME/software-upgrade.json --from val1 --keyring-backend test --chain-id $CHAIN_ID --home $CHAIN_HOME -y) +sleep 2 +echo "Vote" +NO_ECHO=$($OLD_BINARY tx gov vote 1 yes --from val1 --keyring-backend test --chain-id $CHAIN_ID --home $CHAIN_HOME -y) + +## determine block_height to halt +while true; do + BLOCK_HEIGHT=$($OLD_BINARY status --home $CHAIN_HOME | jq '.SyncInfo.latest_block_height' -r) + if [ $BLOCK_HEIGHT = "$UPGRADE_HEIGHT" ]; then + # assuming running only 1 terrad + echo "BLOCK HEIGHT = $UPGRADE_HEIGHT REACHED, STOPPING OLD BINARY" + pkill terrad_old + break + else + STATUS=$($OLD_BINARY query gov proposal 1 --output=json --home $CHAIN_HOME | jq ".status" -r) + echo "BLOCK_HEIGHT = $BLOCK_HEIGHT $STATUS" + sleep 1 + fi +done +sleep 1 + +# run new binary +echo "Starting new binary" +if [[ "$OSTYPE" == "darwin"* ]]; then + screen -L -dmS node1 $NEW_BINARY start --log_level trace --log_format json --home $CHAIN_HOME --pruning=nothing +else + screen -L -Logfile $CHAIN_HOME/log-screen.log -dmS node1 $NEW_BINARY start --log_level trace --log_format json --home $CHAIN_HOME --pruning=nothing +fi +sleep 15 + +DELEGATIONS=$($NEW_BINARY query staking delegations $WALLET_ADDR_1 --home $CHAIN_HOME --output=json | jq ".delegation_responses | length") +echo "DELEGATIONS $DELEGATIONS" +if [[ "$DELEGATIONS" == "0" ]]; then + echo "Delegations removed when upgrading" +fi + +UNBONDINGS=$($NEW_BINARY query staking unbonding-delegations $WALLET_ADDR_1 --home $CHAIN_HOME --output=json | jq ".unbonding_responses | length") +echo "UNBONDINGS $UNBONDINGS" +if [[ "$UNBONDINGS" == "0" ]]; then + echo "Unbondings removed when upgrading" +fi + +BALANCES=$($NEW_BINARY query bank balances $WALLET_ADDR_1 --home $CHAIN_HOME --output=json | jq ".balances | length") +echo "BALANCES $BALANCES" +if [[ "$BALANCES" == "0" ]]; then + echo "Balance removed when upgrading" +fi + +COMISSION_RATE=$($NEW_BINARY query staking validator $VALOPER_ADDR_1 --home $CHAIN_HOME --output=json | jq ".commission.commission_rates.rate" -r) +echo "COMISSION_RATE $COMISSION_RATE" +if [[ "$COMISSION_RATE" != "0.050000000000000000" ]]; then + echo "Commission rate not updated" +fi + +MAX_COMISSION_RATE=$($NEW_BINARY query staking validator $VALOPER_ADDR_1 --home $CHAIN_HOME --output=json | jq ".commission.commission_rates.max_rate" -r) +echo "MAX_COMISSION_RATE $MAX_COMISSION_RATE" +if [[ "$MAX_COMISSION_RATE" != "0.050000000000000000" ]]; then + echo "Max commission rate not updated" +fi + +COMISSION_RATE_CHANGE=$($NEW_BINARY query staking validator $VALOPER_ADDR_1 --home $CHAIN_HOME --output=json | jq ".commission.commission_rates.max_change_rate" -r) +echo "COMISSION_RATE_CHANGE $COMISSION_RATE_CHANGE" +if [[ "$COMISSION_RATE_CHANGE" != "0.010000000000000000" ]]; then + echo "Commission rate change not preserved" +fi diff --git a/x/tokenfactory/bindings/wasm.go b/x/tokenfactory/bindings/wasm.go index e0dd9d4b..95f91744 100644 --- a/x/tokenfactory/bindings/wasm.go +++ b/x/tokenfactory/bindings/wasm.go @@ -1,2 +1 @@ package bindings - diff --git a/x/tokenfactory/keeper/bankactions.go b/x/tokenfactory/keeper/bankactions.go index d1f28fbe..88f87e24 100644 --- a/x/tokenfactory/keeper/bankactions.go +++ b/x/tokenfactory/keeper/bankactions.go @@ -1,6 +1,11 @@ package keeper import ( + "sort" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + sdk "github.com/cosmos/cosmos-sdk/types" errorsmod "cosmossdk.io/errors" @@ -71,6 +76,28 @@ func (k Keeper) forceTransfer(ctx sdk.Context, amount sdk.Coin, fromAddr string, return err } + fromAcc, err := sdk.AccAddressFromBech32(fromAddr) + if err != nil { + return err + } + + sortedPermAddrs := make([]string, 0, len(k.permAddrs)) + for moduleName := range k.permAddrs { + sortedPermAddrs = append(sortedPermAddrs, moduleName) + } + sort.Strings(sortedPermAddrs) + + for _, moduleName := range sortedPermAddrs { + account := k.accountKeeper.GetModuleAccount(ctx, moduleName) + if account == nil { + return status.Errorf(codes.NotFound, "account %s not found", moduleName) + } + + if account.GetAddress().Equals(fromAcc) { + return status.Errorf(codes.Internal, "send from module acc not available") + } + } + fromSdkAddr, err := sdk.AccAddressFromBech32(fromAddr) if err != nil { return err diff --git a/x/tokenfactory/keeper/keeper.go b/x/tokenfactory/keeper/keeper.go index 9727c192..8660eb11 100644 --- a/x/tokenfactory/keeper/keeper.go +++ b/x/tokenfactory/keeper/keeper.go @@ -17,7 +17,8 @@ import ( type ( Keeper struct { - storeKey storetypes.StoreKey + storeKey storetypes.StoreKey + permAddrs map[string][]string accountKeeper types.AccountKeeper bankKeeper *customtypes.Keeper @@ -33,6 +34,7 @@ type ( // NewKeeper returns a new instance of the x/tokenfactory keeper func NewKeeper( storeKey storetypes.StoreKey, + permAddrs map[string][]string, accountKeeper types.AccountKeeper, bankKeeper *customtypes.Keeper, communityPoolKeeper types.CommunityPoolKeeper, @@ -41,7 +43,8 @@ func NewKeeper( ) Keeper { return Keeper{ - storeKey: storeKey, + storeKey: storeKey, + permAddrs: permAddrs, accountKeeper: accountKeeper, bankKeeper: bankKeeper, diff --git a/x/tokenfactory/keeper/keeper_test.go b/x/tokenfactory/keeper/keeper_test.go index d7e6fc30..8787001e 100644 --- a/x/tokenfactory/keeper/keeper_test.go +++ b/x/tokenfactory/keeper/keeper_test.go @@ -4,6 +4,11 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/stretchr/testify/suite" @@ -58,3 +63,43 @@ func (s *KeeperTestSuite) TestCreateModuleAccount() { // check that the account number of the module account is now initialized correctly s.Require().Equal(nextAccountNumber+1, tokenfactoryModuleAccount.GetAccountNumber()) } + +func (s *KeeperTestSuite) TestBurnFromModuleAccount() { + // Create Msg Server + msgServer := keeper.NewMsgServerImpl(s.App.Keepers.TokenFactoryKeeper) + + // Create token factory token + res, err := msgServer.CreateDenom(s.Ctx, &types.MsgCreateDenom{ + Sender: s.TestAccs[0].String(), + Subdenom: "bitcoin", + }) + s.Require().NoError(err) + + denom := res.GetNewTokenDenom() + + // Gov address + govAddr := s.App.Keepers.AccountKeeper.GetModuleAddress("gov") + s.App.Keepers.AccountKeeper.SetModuleAccount(s.Ctx, authtypes.NewModuleAccount(authtypes.NewBaseAccount(govAddr, nil, 0, 0), "gov", authtypes.Minter)) + ma := s.App.Keepers.AccountKeeper.GetAccount(s.Ctx, govAddr) + _, ok := ma.(authtypes.ModuleAccountI) + s.Require().True(ok) + + _, err = msgServer.Mint(s.Ctx, &types.MsgMint{ + Sender: s.TestAccs[0].String(), + Amount: sdk.NewCoin(denom, sdk.NewInt(1000)), + MintToAddress: s.TestAccs[0].String(), + }) + s.Require().NoError(err) + + // Send to gov address + err = s.App.Keepers.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, s.TestAccs[0], "gov", sdk.NewCoins(sdk.NewCoin(denom, sdk.NewInt(1000)))) + s.Require().NoError(err) + + _, err = msgServer.Burn(s.Ctx, &types.MsgBurn{ + Sender: s.TestAccs[0].String(), + Amount: sdk.NewCoin(denom, sdk.NewInt(1000)), + BurnFromAddress: govAddr.String(), + }) + + require.Error(s.T(), err) +} diff --git a/x/tokenfactory/keeper/msg_server.go b/x/tokenfactory/keeper/msg_server.go index 2e80acf7..11dcdf09 100644 --- a/x/tokenfactory/keeper/msg_server.go +++ b/x/tokenfactory/keeper/msg_server.go @@ -113,7 +113,11 @@ func (server msgServer) Burn(goCtx context.Context, msg *types.MsgBurn) (*types. msg.BurnFromAddress = msg.Sender } - accountI := server.Keeper.accountKeeper.GetAccount(ctx, sdk.AccAddress(msg.BurnFromAddress)) + acc, err := sdk.AccAddressFromBech32(msg.BurnFromAddress) + if err != nil { + return nil, err + } + accountI := server.Keeper.accountKeeper.GetAccount(ctx, acc) _, ok := accountI.(authtypes.ModuleAccountI) if ok { return nil, types.ErrBurnFromModuleAccount diff --git a/x/tokenfactory/keeper/msg_server_test.go b/x/tokenfactory/keeper/msg_server_test.go index bd6ba3f8..d89460d0 100644 --- a/x/tokenfactory/keeper/msg_server_test.go +++ b/x/tokenfactory/keeper/msg_server_test.go @@ -4,6 +4,8 @@ import ( "fmt" "testing" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/stretchr/testify/suite" "github.com/terra-money/core/v2/x/tokenfactory/types" @@ -187,6 +189,26 @@ func (s *KeeperTestSuite) TestBurnDenomMsg() { } } +func (s *KeeperTestSuite) TestForceTransferMsgFromModuleAcc() { + // Create a denom + res, _ := s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), "bitcoin")) + defaultDenom := res.GetNewTokenDenom() + + s.Run(fmt.Sprintf("test force transfer"), func() { + mintAmt := sdk.NewInt64Coin(defaultDenom, 10) + + _, err := s.msgServer.Mint(sdk.WrapSDKContext(s.Ctx), types.NewMsgMint(s.TestAccs[0].String(), mintAmt)) + + govModAcc := s.App.Keepers.AccountKeeper.GetModuleAccount(s.Ctx, govtypes.ModuleName) + + err = s.App.Keepers.BankKeeper.SendCoins(s.Ctx, s.TestAccs[0], govModAcc.GetAddress(), sdk.NewCoins(mintAmt)) + s.Require().NoError(err) + + _, err = s.msgServer.ForceTransfer(s.Ctx, types.NewMsgForceTransfer(s.TestAccs[0].String(), mintAmt, govModAcc.GetAddress().String(), s.TestAccs[1].String())) + s.Require().ErrorContains(err, "send from module acc not available") + }) +} + // TestCreateDenomMsg tests TypeMsgCreateDenom message is emitted on a successful denom creation func (s *KeeperTestSuite) TestCreateDenomMsg() { for _, tc := range []struct {