Unverified Commit 16e3b51f authored by Dev Ojha's avatar Dev Ojha Committed by GitHub
Browse files

Merge pull request #1481 from osmosis-labs/mattverse/gov-unpool

v8 Emergency proposals upgrade
parents 45ac35ae 6f140b38
Showing with 592 additions and 29 deletions
+592 -29
package app
import (
wasm "github.com/CosmWasm/wasmd/x/wasm"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
sdk "github.com/cosmos/cosmos-sdk/types"
ante "github.com/cosmos/cosmos-sdk/x/auth/ante"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
channelkeeper "github.com/cosmos/ibc-go/v2/modules/core/04-channel/keeper"
ibcante "github.com/cosmos/ibc-go/v2/modules/core/ante"
wasm "github.com/CosmWasm/wasmd/x/wasm"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
v8 "github.com/osmosis-labs/osmosis/v7/app/upgrades/v8"
txfeeskeeper "github.com/osmosis-labs/osmosis/v7/x/txfees/keeper"
txfeestypes "github.com/osmosis-labs/osmosis/v7/x/txfees/types"
)
......@@ -31,11 +30,13 @@ func NewAnteHandler(
) sdk.AnteHandler {
mempoolFeeOptions := txfeestypes.NewMempoolFeeOptions(appOpts)
mempoolFeeDecorator := txfeeskeeper.NewMempoolFeeDecorator(*txFeesKeeper, mempoolFeeOptions)
return sdk.ChainAnteDecorators(
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
wasmkeeper.NewLimitSimulationGasDecorator(wasmConfig.SimulationGasLimit),
wasmkeeper.NewCountTXDecorator(txCounterStoreKey),
ante.NewRejectExtensionOptionsDecorator(),
v8.MsgFilterDecorator{},
// Use Mempool Fee Decorator from our txfees module instead of default one from auth
// https://github.com/cosmos/cosmos-sdk/blob/master/x/auth/middleware/fee.go#L34
mempoolFeeDecorator,
......
......@@ -2,8 +2,10 @@ package app
import (
sdk "github.com/cosmos/cosmos-sdk/types"
v3 "github.com/osmosis-labs/osmosis/v7/app/upgrades/v3"
v6 "github.com/osmosis-labs/osmosis/v7/app/upgrades/v6"
v8 "github.com/osmosis-labs/osmosis/v7/app/upgrades/v8"
)
// BeginBlockForks is intended to be ran in
......@@ -13,6 +15,8 @@ func BeginBlockForks(ctx sdk.Context, app *OsmosisApp) {
v3.RunForkLogic(ctx, app.GovKeeper, app.StakingKeeper)
case v6.UpgradeHeight:
v6.RunForkLogic(ctx)
case v8.UpgradeHeight:
v8.RunForkLogic(ctx, app.SuperfluidKeeper, app.PoolIncentivesKeeper, app.GAMMKeeper)
default:
// do nothing
return
......
......@@ -334,7 +334,7 @@ func (app *OsmosisApp) InitNormalKeepers(
app.SuperfluidKeeper = superfluidkeeper.NewKeeper(
appCodec, keys[superfluidtypes.StoreKey], app.GetSubspace(superfluidtypes.ModuleName),
*app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.DistrKeeper, app.EpochsKeeper, app.LockupKeeper, gammKeeper, app.IncentivesKeeper,
*app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.DistrKeeper, app.EpochsKeeper, app.LockupKeeper, app.GAMMKeeper, app.IncentivesKeeper,
lockupkeeper.NewMsgServerImpl(app.LockupKeeper))
mintKeeper := mintkeeper.NewKeeper(
......
# V8 Upgrade
The v8 upgrade is an emergency upgrade coordinated according to osmosis governance proposals [225](https://www.mintscan.io/osmosis/proposals/225), [226](https://www.mintscan.io/osmosis/proposals/226). And thus by implication of 225, incentive proposals [222](https://www.mintscan.io/osmosis/proposals/222), [223](https://www.mintscan.io/osmosis/proposals/223), and [224](https://www.mintscan.io/osmosis/proposals/224).
## Adjusting Incentives for 222, 223, 224
Like the weekly Gauge Weight updates, the implementations for these proposals simply modify the weights of gauges between pools in the Upgrade:
* `ApplyProp222Change`
* `ApplyProp223Change`
* `ApplyProp224Change`
The specification of Minimum and Maximum values will be applied to the spreadsheet that is shared in each Weekly update.
## UnPoolWhitelistedPool for 226
The implementation of 226 will introduce a new method for unpooling:
`UnPoolWhitelistedPool`
Let's review the states a position in a pool may be to be able to understand the unpooling process better. Coins are pooled together to form shares of a GAMM. These may be locked for a period of time, to receive addtional incentives. Finally, locked tokens may enter into Superfluid Delegations.
```
┌─────────────────────────┐ ┌─────────────────────────┐
│ sdk.Coin │ │ sdk.Coin │
│ Denom: UST │ │ Denom: uOSMO │
│ Amount: 5.647 │ │ Amount: 1 │
└───────────┬─────────────┘ └───────────┬─────────────┘
│ │
┌───────────▼─────────────────────────────▼─────────────┐
│ JoinPool() │
└───────────────────────────┬───────────────────────────┘
┌────────────▼────────────┐
│ sdk.Coin │
│ Denom: GAMM │
│ Amount: 100000 │
└────────────┬────────────┘
┌───────────────────────────▼───────────────────────────┐
│ LockTokens() │
└───────────────────────────┬───────────────────────────┘
┌────────────▼────────────┐
│ types.PeriodLock │
└────────────┬────────────┘
┌───────────────────────────▼───────────────────────────┐
│ SuperfluidDelegate() │
└───────────────────────────┬───────────────────────────┘
┌────────────▼────────────┐
│ types.SuperfluidAsset │
└─────────────────────────┘
```
### Unpooling Steps
To unpool, we'll need to carefully consider each of these concepts above. For instance, a user may have already begun unbonding.
We will start with the most deeply locked assets, and iteratively unroll them until we end up with individual sdk.Coin entities, some of which may be locked.
In the code, the following comment block may be found:
```
// 0) Check if its for a whitelisted unpooling poolID
// 1) Consistency check that lockID corresponds to sender, and contains correct LP shares. (Should also be validated by caller)
// 2) Get remaining duration on the lock.
// 3) If superfluid delegated, superfluid undelegate
// 4) Break underlying lock. This will clear any metadata if things are superfluid unbonding
// 5) ExitPool with these unlocked LP shares
// 6) Make 1 new lock for every asset in collateral. Many code paths need this assumption to hold
// 7) Make new lock begin unlocking
```
\ No newline at end of file
package v8
import v8constants "github.com/osmosis-labs/osmosis/v7/app/upgrades/v8/constants"
const (
// UpgradeName defines the on-chain upgrade name for the Osmosis v8 upgrade.
UpgradeName = v8constants.UpgradeName
// UpgradeHeight defines the block height at which the Osmosis v8 upgrade is
// triggered.
UpgradeHeight = v8constants.UpgradeHeight
)
// package v8constants contains constants related to the v8 upgrade.
// It is in its own package to eliminate import cycle issues.
package v8constants
const (
// UpgradeName defines the on-chain upgrade name for the Osmosis v8 upgrade.
UpgradeName = "v8"
// UpgradeHeight defines the block height at which the Osmosis v8 upgrade is
// triggered.
// Block height 4_402_000, approximately 4PM UTC, May 15th
UpgradeHeight = 4402000
)
package v8
import (
sdk "github.com/cosmos/cosmos-sdk/types"
gammkeeper "github.com/osmosis-labs/osmosis/v7/x/gamm/keeper"
poolincentiveskeeper "github.com/osmosis-labs/osmosis/v7/x/pool-incentives/keeper"
superfluidkeeper "github.com/osmosis-labs/osmosis/v7/x/superfluid/keeper"
)
// RunForkLogic executes height-gated on-chain fork logic for the Osmosis v8
// upgrade.
func RunForkLogic(ctx sdk.Context, superfluid *superfluidkeeper.Keeper, poolincentives *poolincentiveskeeper.Keeper, gamm *gammkeeper.Keeper) {
for i := 0; i < 100; i++ {
ctx.Logger().Info("I am upgrading to v8")
}
ctx.Logger().Info("Applying Osmosis v8 upgrade. Allowing direct unpooling for whitelisted pools")
ctx.Logger().Info("Applying accelerated incentive updates per proposal 225")
ApplyProp222Change(ctx, poolincentives)
ApplyProp223Change(ctx, poolincentives)
ApplyProp224Change(ctx, poolincentives)
ctx.Logger().Info("Registering state change for whitelisted pools for unpooling per proposal 226 ")
RegisterWhitelistedDirectUnbondPools(ctx, superfluid, gamm)
}
package v8
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/v7/osmoutils"
poolincentiveskeeper "github.com/osmosis-labs/osmosis/v7/x/pool-incentives/keeper"
poolincentivestypes "github.com/osmosis-labs/osmosis/v7/x/pool-incentives/types"
)
// This file implements logic for accelerated incentive proposals.
// This function is common to all the props,
// executing the equivalent result of the "UpdatePoolIncentives" proposals, inside of this upgrade logic.
func applyPoolIncentivesUpdate(ctx sdk.Context, poolincentiveskeeper *poolincentiveskeeper.Keeper, records []poolincentivestypes.DistrRecord) {
// Notice that the pool incentives update proposal code, just calls UpdateDistrRecords:
// https://github.com/osmosis-labs/osmosis/blob/v7.3.0/x/pool-incentives/keeper/gov.go#L13-L15
// And that p.Records is the field output by the gov queries.
// If error, undo state update, log, and proceed. We don't want to stop the entire upgrade due to
// an unexpected error here.
_ = osmoutils.ApplyFuncIfNoError(ctx, func(wrappedCtx sdk.Context) error {
err := poolincentiveskeeper.UpdateDistrRecords(wrappedCtx, records...)
if err != nil {
ctx.Logger().Error("Something has happened, prop update did not apply. Continuing to proceed with other components of the upgrade.")
}
return err
})
}
// Apply prop 222 change
func ApplyProp222Change(ctx sdk.Context, poolincentiveskeeper *poolincentiveskeeper.Keeper) {
// Pool records obtained right off proposal
// osmosisd q gov proposal 222
// records:
// - gauge_id: "1718"
// weight: "9138119"
// - gauge_id: "1719"
// weight: "5482871"
// - gauge_id: "1720"
// weight: "3655247"
// - gauge_id: "2965"
// weight: "9138119"
// - gauge_id: "2966"
// weight: "5482872"
// - gauge_id: "2967"
// weight: "3655248"
// _PLEASE_ double check these numbers, and double the check the proposals choice itself
records := []poolincentivestypes.DistrRecord{
{GaugeId: 1718, Weight: sdk.NewInt(9138119)},
{GaugeId: 1719, Weight: sdk.NewInt(5482871)},
{GaugeId: 1720, Weight: sdk.NewInt(3655247)},
{GaugeId: 2965, Weight: sdk.NewInt(9138119)},
{GaugeId: 2966, Weight: sdk.NewInt(5482872)},
{GaugeId: 2967, Weight: sdk.NewInt(3655248)},
}
ctx.Logger().Info("Applying proposal 222 update")
applyPoolIncentivesUpdate(ctx, poolincentiveskeeper, records)
}
// Apply prop 223 change
func ApplyProp223Change(ctx sdk.Context, poolincentiveskeeper *poolincentiveskeeper.Keeper) {
// Pool records obtained right off proposal
// osmosisd q gov proposal 223
// records:
// - gauge_id: "1721"
// weight: "2831977"
// - gauge_id: "1722"
// weight: "1699186"
// - gauge_id: "1723"
// weight: "1132791"
// - gauge_id: "3383"
// weight: "2831978"
// - gauge_id: "3384"
// weight: "1699187"
// - gauge_id: "3385"
// weight: "1132791"
// _PLEASE_ double check these numbers, and double the check the proposals choice itself
records := []poolincentivestypes.DistrRecord{
{GaugeId: 1721, Weight: sdk.NewInt(2831977)},
{GaugeId: 1722, Weight: sdk.NewInt(1699186)},
{GaugeId: 1723, Weight: sdk.NewInt(1132791)},
{GaugeId: 3383, Weight: sdk.NewInt(2831978)},
{GaugeId: 3384, Weight: sdk.NewInt(1699187)},
{GaugeId: 3385, Weight: sdk.NewInt(1132791)},
}
ctx.Logger().Info("Applying proposal 223 update")
applyPoolIncentivesUpdate(ctx, poolincentiveskeeper, records)
}
// Apply prop 224 change
func ApplyProp224Change(ctx sdk.Context, poolincentiveskeeper *poolincentiveskeeper.Keeper) {
// Pool records obtained right off proposal
// osmosisd q gov proposal 224
// records:
// - gauge_id: "1724"
// weight: "1881159"
// - gauge_id: "1725"
// weight: "1128695"
// - gauge_id: "1726"
// weight: "752463"
// - gauge_id: "2949"
// weight: "1881160"
// - gauge_id: "2950"
// weight: "1128696"
// - gauge_id: "2951"
// weight: "752464"
// _PLEASE_ double check these numbers, and double the check the proposals choice itself
records := []poolincentivestypes.DistrRecord{
{GaugeId: 1724, Weight: sdk.NewInt(1881159)},
{GaugeId: 1725, Weight: sdk.NewInt(1128695)},
{GaugeId: 1726, Weight: sdk.NewInt(752463)},
{GaugeId: 2949, Weight: sdk.NewInt(1881160)},
{GaugeId: 2950, Weight: sdk.NewInt(1128696)},
{GaugeId: 2951, Weight: sdk.NewInt(752464)},
}
ctx.Logger().Info("Applying proposal 224 update")
applyPoolIncentivesUpdate(ctx, poolincentiveskeeper, records)
}
package v8
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
superfluidtypes "github.com/osmosis-labs/osmosis/v7/x/superfluid/types"
)
// MsgFilterDecorator defines an AnteHandler decorator for the v8 upgrade that
// provide height-gated message filtering acceptance.
type MsgFilterDecorator struct{}
// AnteHandle performs an AnteHandler check that returns an error if the current
// block height is less than the v8 upgrade height and contains messages that are
// not supported until the upgrade height is reached.
func (mfd MsgFilterDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
currHeight := ctx.BlockHeight()
if currHeight < UpgradeHeight && hasInvalidMsgs(tx.GetMsgs()) {
return ctx, fmt.Errorf("tx contains unsupported message types at height %d", currHeight)
}
return next(ctx, tx, simulate)
}
func hasInvalidMsgs(msgs []sdk.Msg) bool {
for _, msg := range msgs {
switch msg.(type) {
case *superfluidtypes.MsgUnPoolWhitelistedPool:
return true
}
}
return false
}
package v8_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/stretchr/testify/require"
"github.com/osmosis-labs/osmosis/v7/app"
v8 "github.com/osmosis-labs/osmosis/v7/app/upgrades/v8"
superfluidtypes "github.com/osmosis-labs/osmosis/v7/x/superfluid/types"
)
func noOpAnteDecorator() sdk.AnteHandler {
return func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) {
return ctx, nil
}
}
func TestMsgFilterDecorator(t *testing.T) {
handler := v8.MsgFilterDecorator{}
txCfg := app.MakeEncodingConfig().TxConfig
addr1 := sdk.AccAddress([]byte("addr1_______________"))
addr2 := sdk.AccAddress([]byte("addr2_______________"))
testCases := []struct {
name string
ctx sdk.Context
msgs []sdk.Msg
expectErr bool
}{
{
name: "valid tx pre-upgrade",
ctx: sdk.Context{}.WithBlockHeight(v8.UpgradeHeight - 1),
msgs: []sdk.Msg{
banktypes.NewMsgSend(addr1, addr2, sdk.NewCoins(sdk.NewInt64Coin("foo", 5))),
},
expectErr: false,
},
{
name: "invalid tx pre-upgrade",
ctx: sdk.Context{}.WithBlockHeight(v8.UpgradeHeight - 1),
msgs: []sdk.Msg{
banktypes.NewMsgSend(addr1, addr2, sdk.NewCoins(sdk.NewInt64Coin("foo", 5))),
superfluidtypes.NewMsgUnPoolWhitelistedPool(addr1, 1),
},
expectErr: true,
},
{
name: "valid tx post-upgrade",
ctx: sdk.Context{}.WithBlockHeight(v8.UpgradeHeight),
msgs: []sdk.Msg{
banktypes.NewMsgSend(addr1, addr2, sdk.NewCoins(sdk.NewInt64Coin("foo", 5))),
superfluidtypes.NewMsgUnPoolWhitelistedPool(addr1, 1),
},
expectErr: false,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
txBuilder := txCfg.NewTxBuilder()
require.NoError(t, txBuilder.SetMsgs(tc.msgs...))
_, err := handler.AnteHandle(tc.ctx, txBuilder.GetTx(), false, noOpAnteDecorator())
if tc.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
package v8
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
gammkeeper "github.com/osmosis-labs/osmosis/v7/x/gamm/keeper"
superfluidkeeper "github.com/osmosis-labs/osmosis/v7/x/superfluid/keeper"
)
const ustDenom = "ibc/BE1BB42D4BE3C30D50B68D7C41DB4DFCE9678E8EF8C539F6E6A9345048894FCC"
// RegisterWhitelistedDirectUnbondPools registers pools that are allowed to unpool
// https://www.mintscan.io/osmosis/proposals/226
// osmosisd q gov proposal 226
func RegisterWhitelistedDirectUnbondPools(ctx sdk.Context, superfluid *superfluidkeeper.Keeper, gamm *gammkeeper.Keeper) {
// These are the pools listed in the proposal. Proposal raw text for the listing of UST pools:
// The list of pools affected are defined below:
// #560 (UST/OSMO)
// #562 (UST/LUNA)
// #567 (UST/EEUR)
// #578 (UST/XKI)
// #592 (UST/BTSG)
// #610 (UST/CMDX)
// #612 (UST/XPRT)
// #615 (UST/LUM)
// #642 (UST/UMEE)
// #679 (UST/axl-FRAX/axl-USDT/axl-USDC)
// #580 (UST/JUNO)
// #635 (UST/g-USDC/g-DAI)
//
// Added pools #580 and #635
// Jacob, Geo are in favor of adding the not listed UST pools with > 1k liquidity.
// TODO: Circulate to validators to get agreement.
whitelistedPoolShares := []uint64{560, 562, 567, 578, 592, 610, 612, 615, 642, 679, 580, 635}
// Consistency check that each whitelisted pool contains UST
for _, whitelistedPool := range whitelistedPoolShares {
if err := CheckPoolContainsUST(ctx, gamm, whitelistedPool); err != nil {
panic(err)
}
}
superfluid.SetUnpoolAllowedPools(ctx, whitelistedPoolShares)
}
// CheckPoolContainsUST looks up the pool from the gammkeeper and
// returns nil if the pool contains UST's ibc denom
// returns an error if the pool does not contain UST's ibc denom or on any other error case.
func CheckPoolContainsUST(ctx sdk.Context, gamm *gammkeeper.Keeper, poolID uint64) error {
pool, err := gamm.GetPool(ctx, poolID)
if err != nil {
return err
}
assets := pool.GetAllPoolAssets()
for _, asset := range assets {
if asset.Token.Denom == ustDenom {
return nil
}
}
return fmt.Errorf("pool with ID %d does not contain UST", poolID)
}
......@@ -65,3 +65,5 @@ message LockIdIntermediaryAccountConnection {
uint64 lock_id = 1;
string intermediary_account = 2;
}
message UnpoolWhitelistedPools { repeated uint64 ids = 1; }
......@@ -28,6 +28,8 @@ service Msg {
// Execute lockup lock and superfluid delegation in a single msg
rpc LockAndSuperfluidDelegate(MsgLockAndSuperfluidDelegate)
returns (MsgLockAndSuperfluidDelegateResponse);
rpc UnPoolWhitelistedPool(MsgUnPoolWhitelistedPool)
returns (MsgUnPoolWhitelistedPoolResponse);
}
message MsgSuperfluidDelegate {
......@@ -67,4 +69,19 @@ message MsgLockAndSuperfluidDelegate {
];
string val_addr = 3;
}
message MsgLockAndSuperfluidDelegateResponse { uint64 ID = 1; }
\ No newline at end of file
message MsgLockAndSuperfluidDelegateResponse { uint64 ID = 1; }
// MsgUnPoolWhitelistedPool Unpools every lock the sender has, that is
// associated with pool pool_id. If pool_id is not approved for unpooling by
// governance, this is a no-op. Unpooling takes the locked gamm shares, and runs
// "ExitPool" on it, to get the constituent tokens. e.g. z gamm/pool/1 tokens
// ExitPools into constituent tokens x uatom, y uosmo. Then it creates a new
// lock for every constituent token, with the duration associated with the lock.
// If the lock was unbonding, the new lockup durations should be the time left
// until unbond completion.
message MsgUnPoolWhitelistedPool {
string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ];
uint64 pool_id = 2 [ (gogoproto.moretags) = "yaml:\"pool_id\"" ];
}
message MsgUnPoolWhitelistedPoolResponse { repeated uint64 exitedLockIds = 1; }
......@@ -93,7 +93,7 @@ func (server msgServer) ExitPool(goCtx context.Context, msg *types.MsgExitPool)
return nil, err
}
err = server.keeper.ExitPool(ctx, sender, msg.PoolId, msg.ShareInAmount, msg.TokenOutMins)
_, err = server.keeper.ExitPool(ctx, sender, msg.PoolId, msg.ShareInAmount, msg.TokenOutMins)
if err != nil {
return nil, err
}
......
......@@ -311,10 +311,10 @@ func (k Keeper) ExitPool(
poolId uint64,
shareInAmount sdk.Int,
tokenOutMins sdk.Coins,
) (err error) {
) (exitCoins sdk.Coins, err error) {
pool, err := k.GetPool(ctx, poolId)
if err != nil {
return err
return sdk.Coins{}, err
}
totalSharesAmount := pool.GetTotalShares().Amount
......@@ -323,7 +323,7 @@ func (k Keeper) ExitPool(
shareRatio := shareInAmountAfterExitFee.ToDec().QuoInt(totalSharesAmount)
if shareRatio.LTE(sdk.ZeroDec()) {
return sdkerrors.Wrapf(types.ErrInvalidMathApprox, "share ratio is zero or negative")
return sdk.Coins{}, sdkerrors.Wrapf(types.ErrInvalidMathApprox, "share ratio is zero or negative")
}
// Assume that the tokenInMaxAmounts is validated.
......@@ -339,13 +339,13 @@ func (k Keeper) ExitPool(
for _, PoolAsset := range PoolAssets {
tokenOutAmount := shareRatio.MulInt(PoolAsset.Token.Amount).TruncateInt()
if tokenOutAmount.LTE(sdk.ZeroInt()) {
return sdkerrors.Wrapf(types.ErrInvalidMathApprox, "token amount is zero or negative")
return sdk.Coins{}, sdkerrors.Wrapf(types.ErrInvalidMathApprox, "token amount is zero or negative")
}
// Check if a minimum token amount is specified for this token,
// and if so ensure that the minimum is less than the amount returned.
if tokenOutMinAmount, ok := tokenOutMinMap[PoolAsset.Token.Denom]; ok && tokenOutAmount.LT(tokenOutMinAmount) {
return sdkerrors.Wrapf(types.ErrLimitMinAmount, "%s token is lesser than min amount", PoolAsset.Token.Denom)
return sdk.Coins{}, sdkerrors.Wrapf(types.ErrLimitMinAmount, "%s token is lesser than min amount", PoolAsset.Token.Denom)
}
newPoolCoins = append(newPoolCoins,
......@@ -355,12 +355,12 @@ func (k Keeper) ExitPool(
err = pool.UpdatePoolAssetBalances(newPoolCoins)
if err != nil {
return err
return sdk.Coins{}, err
}
err = k.bankKeeper.SendCoins(ctx, pool.GetAddress(), sender, coins)
if err != nil {
return err
return sdk.Coins{}, err
}
// Remove the exit fee shares from the pool.
......@@ -368,25 +368,25 @@ func (k Keeper) ExitPool(
if exitFee.IsPositive() {
err = k.BurnPoolShareFromAccount(ctx, pool, sender, exitFee)
if err != nil {
return err
return sdk.Coins{}, err
}
}
err = k.BurnPoolShareFromAccount(ctx, pool, sender, shareInAmountAfterExitFee)
if err != nil {
return err
return sdk.Coins{}, err
}
err = k.SetPool(ctx, pool)
if err != nil {
return err
return sdk.Coins{}, err
}
k.createRemoveLiquidityEvent(ctx, sender, pool.GetId(), coins)
k.hooks.AfterExitPool(ctx, sender, pool.GetId(), shareInAmount, coins)
k.RecordTotalLiquidityDecrease(ctx, coins)
return nil
return coins, nil
}
func (k Keeper) ExitSwapShareAmountIn(
......
......@@ -6,6 +6,7 @@ import (
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/v7/x/gamm/pool-models/balancer"
"github.com/osmosis-labs/osmosis/v7/x/gamm/types"
)
......@@ -333,7 +334,7 @@ func (suite *KeeperTestSuite) TestExitPool() {
fn: func(poolId uint64) {
keeper := suite.app.GAMMKeeper
// Acc2 has no share token.
err := keeper.ExitPool(suite.ctx, acc2, poolId, types.OneShare.MulRaw(50), sdk.Coins{})
_, err := keeper.ExitPool(suite.ctx, acc2, poolId, types.OneShare.MulRaw(50), sdk.Coins{})
suite.Require().Error(err)
},
},
......@@ -342,7 +343,7 @@ func (suite *KeeperTestSuite) TestExitPool() {
keeper := suite.app.GAMMKeeper
balancesBefore := suite.app.BankKeeper.GetAllBalances(suite.ctx, acc1)
err := keeper.ExitPool(suite.ctx, acc1, poolId, types.InitPoolSharesSupply.QuoRaw(2), sdk.Coins{})
_, err := keeper.ExitPool(suite.ctx, acc1, poolId, types.InitPoolSharesSupply.QuoRaw(2), sdk.Coins{})
suite.Require().NoError(err)
// (100 - 50) * OneShare should remain.
suite.Require().Equal(types.InitPoolSharesSupply.QuoRaw(2).String(), suite.app.BankKeeper.GetBalance(suite.ctx, acc1, "gamm/pool/1").Amount.String())
......@@ -359,7 +360,7 @@ func (suite *KeeperTestSuite) TestExitPool() {
fn: func(poolId uint64) {
keeper := suite.app.GAMMKeeper
err := keeper.ExitPool(suite.ctx, acc1, poolId, sdk.NewInt(0), sdk.Coins{})
_, err := keeper.ExitPool(suite.ctx, acc1, poolId, sdk.NewInt(0), sdk.Coins{})
suite.Require().Error(err, "can't join the pool with requesting 0 share amount")
},
},
......@@ -367,7 +368,7 @@ func (suite *KeeperTestSuite) TestExitPool() {
fn: func(poolId uint64) {
keeper := suite.app.GAMMKeeper
err := keeper.ExitPool(suite.ctx, acc1, poolId, sdk.NewInt(-1), sdk.Coins{})
_, err := keeper.ExitPool(suite.ctx, acc1, poolId, sdk.NewInt(-1), sdk.Coins{})
suite.Require().Error(err, "can't join the pool with requesting negative share amount")
},
},
......@@ -377,7 +378,7 @@ func (suite *KeeperTestSuite) TestExitPool() {
// Test the "tokenOutMins"
// In this case, to refund the 50000000 amount of share token, the foo, bar token are expected to be refunded as 5000 amounts.
err := keeper.ExitPool(suite.ctx, acc1, poolId, types.OneShare.MulRaw(50), sdk.Coins{
_, err := keeper.ExitPool(suite.ctx, acc1, poolId, types.OneShare.MulRaw(50), sdk.Coins{
sdk.NewCoin("foo", sdk.NewInt(5001)),
})
suite.Require().Error(err)
......@@ -389,7 +390,7 @@ func (suite *KeeperTestSuite) TestExitPool() {
// Test the "tokenOutMins"
// In this case, to refund the 50000000 amount of share token, the foo, bar token are expected to be refunded as 5000 amounts.
err := keeper.ExitPool(suite.ctx, acc1, poolId, types.OneShare.MulRaw(50), sdk.Coins{
_, err := keeper.ExitPool(suite.ctx, acc1, poolId, types.OneShare.MulRaw(50), sdk.Coins{
sdk.NewCoin("foo", sdk.NewInt(5000)),
})
suite.Require().NoError(err)
......@@ -450,7 +451,7 @@ func (suite *KeeperTestSuite) TestActiveBalancerPool() {
// uneffected by start time
err = suite.app.GAMMKeeper.JoinPool(suite.ctx, acc1, poolId, types.OneShare.MulRaw(50), sdk.Coins{})
suite.Require().NoError(err)
err = suite.app.GAMMKeeper.ExitPool(suite.ctx, acc1, poolId, types.InitPoolSharesSupply.QuoRaw(2), sdk.Coins{})
_, err = suite.app.GAMMKeeper.ExitPool(suite.ctx, acc1, poolId, types.InitPoolSharesSupply.QuoRaw(2), sdk.Coins{})
suite.Require().NoError(err)
foocoin := sdk.NewCoin("foo", sdk.NewInt(10))
......
......@@ -8,6 +8,7 @@ import (
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/gogo/protobuf/proto"
"github.com/osmosis-labs/osmosis/v7/store"
"github.com/osmosis-labs/osmosis/v7/x/lockup/types"
)
......@@ -486,17 +487,54 @@ func (k Keeper) Unlock(ctx sdk.Context, lock types.PeriodLock) error {
return k.unlockInternalLogic(ctx, lock)
}
// ForceUnlock ignores unlock duration and immediately unlock and refund.
// CONTRACT: should be used only at the chain upgrade script
// TODO: Revisit for Superfluid Staking
// ForceUnlock ignores unlock duration and immediately unlocks the lock and refunds tokens to lock owner..
func (k Keeper) ForceUnlock(ctx sdk.Context, lock types.PeriodLock) error {
// Steps:
// 1) Break associated synthetic locks. (Superfluid data)
// 2) If lock is bonded, move it to unlocking
// 3) Run logic to delete unlocking metadata, and send tokens to owner.
synthLocks := k.GetAllSyntheticLockupsByLockup(ctx, lock.ID)
err := k.BreakAllSyntheticLocks(ctx, lock, synthLocks)
if err != nil {
return err
}
if !lock.IsUnlocking() {
err := k.BeginUnlock(ctx, lock, nil)
if err != nil {
return err
}
}
return k.unlockInternalLogic(ctx, lock)
// NOTE: This caused a bug! BeginUnlock changes the owner the lock.EndTime
// This shows the bad API design of not using lock.ID in every public function.
lockPtr, err := k.GetLockByID(ctx, lock.ID)
if err != nil {
return err
}
return k.unlockInternalLogic(ctx, *lockPtr)
}
func (k Keeper) BreakAllSyntheticLocks(ctx sdk.Context, lock types.PeriodLock, synthLocks []types.SyntheticLock) error {
if len(synthLocks) == 0 {
return nil
}
// Synth locks have data set in two places, accumulation store & setSyntheticLockAndResetRefs
// see that [CreateSyntheticLock](https://github.com/osmosis-labs/osmosis/blob/v7.3.0/x/lockup/keeper/synthetic_lock.go#L105)
// only has 3 set locations:
// - k.setSyntheticLockupObject(ctx, &synthLock)
// - k.addSyntheticLockRefs(ctx, *lock, synthLock)
// - k.accumulationStore(ctx, synthLock.SynthDenom).Increase(accumulationKey(unlockDuration), coin.Amount)
// ALL of which are reverted in the method DeleteSyntheticLock, here:
// https://github.com/osmosis-labs/osmosis/blob/v7.3.0/x/lockup/keeper/synthetic_lock.go#L156
for _, synthLock := range synthLocks {
err := k.DeleteSyntheticLockup(ctx, lock.ID, synthLock.SynthDenom)
if err != nil {
return err
}
}
return nil
}
func (k Keeper) unlockInternalLogic(ctx sdk.Context, lock types.PeriodLock) error {
......
......@@ -33,6 +33,7 @@ func GetTxCmd() *cobra.Command {
NewCmdSubmitSetSuperfluidAssetsProposal(),
NewCmdSubmitRemoveSuperfluidAssetsProposal(),
NewCmdLockAndSuperfluidDelegate(),
NewCmdUnPoolWhitelistedPool(),
)
return cmd
......@@ -371,3 +372,34 @@ func NewCmdLockAndSuperfluidDelegate() *cobra.Command {
flags.AddTxFlagsToCmd(cmd)
return cmd
}
// NewCmdUnPoolWhitelistedPool implements a command handler for unpooling whitelisted pools.
func NewCmdUnPoolWhitelistedPool() *cobra.Command {
cmd := &cobra.Command{
Use: "unpool-whitelisted-pool [pool_id] [flags]",
Short: "unpool whitelisted pool",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever)
sender := clientCtx.GetFromAddress()
poolId, err := strconv.Atoi(args[0])
if err != nil {
return err
}
msg := types.NewMsgUnPoolWhitelistedPool(sender, uint64(poolId))
return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg)
},
}
flags.AddTxFlagsToCmd(cmd)
return cmd
}
......@@ -28,6 +28,9 @@ func NewHandler(k *keeper.Keeper) sdk.Handler {
case *types.MsgLockAndSuperfluidDelegate:
res, err := msgServer.LockAndSuperfluidDelegate(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgUnPoolWhitelistedPool:
res, err := msgServer.UnPoolWhitelistedPool(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
// case *types.MsgSuperfluidRedelegate:
// res, err := msgServer.SuperfluidRedelegate(sdk.WrapSDKContext(ctx), msg)
// return sdk.WrapServiceResult(ctx, res, err)
......
......@@ -2,10 +2,17 @@ package keeper
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
v8constants "github.com/osmosis-labs/osmosis/v7/app/upgrades/v8/constants"
gammtypes "github.com/osmosis-labs/osmosis/v7/x/gamm/types"
lockuptypes "github.com/osmosis-labs/osmosis/v7/x/lockup/types"
"github.com/osmosis-labs/osmosis/v7/x/superfluid/types"
)
......@@ -95,3 +102,42 @@ func (server msgServer) LockAndSuperfluidDelegate(goCtx context.Context, msg *ty
ID: lockupRes.ID,
}, err
}
func (server msgServer) UnPoolWhitelistedPool(goCtx context.Context, msg *types.MsgUnPoolWhitelistedPool) (*types.MsgUnPoolWhitelistedPoolResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
if ctx.BlockHeight() < v8constants.UpgradeHeight {
return nil, errors.New("message not activated")
}
sender, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return nil, err
}
// We get all the lockIDs to unpool
lpShareDenom := gammtypes.GetPoolShareDenom(msg.PoolId)
minimalDuration := time.Millisecond
unpoolLocks := server.keeper.lk.GetAccountLockedLongerDurationDenom(ctx, sender, lpShareDenom, minimalDuration)
allExitedLockIDs := []uint64{}
for _, lock := range unpoolLocks {
exitedLockIDs, err := server.keeper.UnpoolAllowedPools(ctx, sender, msg.PoolId, lock.ID)
if err != nil {
return nil, err
}
allExitedLockIDs = append(allExitedLockIDs, exitedLockIDs...)
}
allExitedLockIDsSerialized, _ := json.Marshal(allExitedLockIDs)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.TypeEvtUnpoolId,
sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender),
sdk.NewAttribute(types.AttributeDenom, lpShareDenom),
sdk.NewAttribute(types.AttributeNewLockIds, string(allExitedLockIDsSerialized)),
),
})
return &types.MsgUnPoolWhitelistedPoolResponse{ExitedLockIds: allExitedLockIDs}, nil
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment