From f24bf7edca35a14f334dcfe184065250dd22975f Mon Sep 17 00:00:00 2001 From: "Matt, Park" <45252226+mattverse@users.noreply.github.com> Date: Tue, 5 Jul 2022 16:29:09 +0900 Subject: [PATCH 1/3] Refactor `lock` method (#1936) * Add lock method refactor * Delete duplciated testing * Update x/lockup/keeper/lock.go Co-authored-by: Aleksandr Bezobchuk * Add tests implement feedback from code review * Add test cases Co-authored-by: Aleksandr Bezobchuk (cherry picked from commit c1155937b4b07d4db16edf29d5222410e291734f) # Conflicts: # x/lockup/keeper/admin_keeper_test.go # x/lockup/keeper/lock.go --- x/lockup/keeper/admin_keeper_test.go | 11 ++- x/lockup/keeper/export_test.go | 4 + x/lockup/keeper/lock.go | 71 ++++++++++++-- x/lockup/keeper/lock_test.go | 137 +++++++++++++++++++++------ 4 files changed, 181 insertions(+), 42 deletions(-) diff --git a/x/lockup/keeper/admin_keeper_test.go b/x/lockup/keeper/admin_keeper_test.go index c10826881e8..565a0dc6959 100644 --- a/x/lockup/keeper/admin_keeper_test.go +++ b/x/lockup/keeper/admin_keeper_test.go @@ -3,8 +3,12 @@ package keeper_test import ( "time" +<<<<<<< HEAD "github.com/osmosis-labs/osmosis/v10/x/lockup/keeper" "github.com/osmosis-labs/osmosis/v10/x/lockup/types" +======= + "github.com/osmosis-labs/osmosis/v7/x/lockup/keeper" +>>>>>>> c1155937 (Refactor `lock` method (#1936)) sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -14,11 +18,10 @@ func (suite *KeeperTestSuite) TestRelock() { addr1 := sdk.AccAddress([]byte("addr1---------------")) coins := sdk.Coins{sdk.NewInt64Coin("stake", 10)} - lock := types.NewPeriodLock(1, addr1, time.Second, suite.Ctx.BlockTime().Add(time.Second), coins) // lock with balance suite.FundAcc(addr1, coins) - err := suite.App.LockupKeeper.Lock(suite.Ctx, lock) + lock, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, addr1, coins, time.Second) suite.Require().NoError(err) // lock with balance with same id @@ -38,12 +41,12 @@ func (suite *KeeperTestSuite) BreakLock() { addr1 := sdk.AccAddress([]byte("addr1---------------")) coins := sdk.Coins{sdk.NewInt64Coin("stake", 10)} - lock := types.NewPeriodLock(1, addr1, time.Second, suite.Ctx.BlockTime().Add(time.Second), coins) // lock with balance suite.FundAcc(addr1, coins) - err := suite.App.LockupKeeper.Lock(suite.Ctx, lock) + lock, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, addr1, coins, time.Second) + suite.Require().NoError(err) // break lock diff --git a/x/lockup/keeper/export_test.go b/x/lockup/keeper/export_test.go index bbb648e6a19..369c6b1812e 100644 --- a/x/lockup/keeper/export_test.go +++ b/x/lockup/keeper/export_test.go @@ -25,3 +25,7 @@ func (k Keeper) SyntheticCoins(coins sdk.Coins, suffix string) sdk.Coins { func (k Keeper) GetCoinsFromLocks(locks []types.PeriodLock) sdk.Coins { return k.getCoinsFromLocks(locks) } + +func (k Keeper) Lock(ctx sdk.Context, lock types.PeriodLock, tokensToLock sdk.Coins) error { + return k.lock(ctx, lock, tokensToLock) +} diff --git a/x/lockup/keeper/lock.go b/x/lockup/keeper/lock.go index 0bb30edf581..540fb33fe6a 100644 --- a/x/lockup/keeper/lock.go +++ b/x/lockup/keeper/lock.go @@ -125,31 +125,41 @@ func (k Keeper) AddToExistingLock(ctx sdk.Context, owner sdk.AccAddress, coin sd return locks, nil } +<<<<<<< HEAD // AddTokensToLock locks more tokens into a lockup // This also saves the lock to the store. func (k Keeper) AddTokensToLockByID(ctx sdk.Context, lockID uint64, owner sdk.AccAddress, coin sdk.Coin) (*types.PeriodLock, error) { +======= +// AddTokensToLock locks additional tokens into an existing lock with the given ID. +// Tokens locked are sent and kept in the module account. +// This method alters the lock state in store, thus we do a sanity check to ensure +// lock owner matches the given owner. +func (k Keeper) AddTokensToLockByID(ctx sdk.Context, lockID uint64, owner sdk.AccAddress, tokensToAdd sdk.Coin) (*types.PeriodLock, error) { +>>>>>>> c1155937 (Refactor `lock` method (#1936)) lock, err := k.GetLockByID(ctx, lockID) + if err != nil { + return nil, err + } if lock.GetOwner() != owner.String() { return nil, types.ErrNotLockOwner } + lock.Coins = lock.Coins.Add(tokensToAdd) + err = k.lock(ctx, *lock, sdk.NewCoins(tokensToAdd)) if err != nil { return nil, err } - if err := k.bk.SendCoinsFromAccountToModule(ctx, lock.OwnerAddress(), types.ModuleName, sdk.NewCoins(coin)); err != nil { - return nil, err - } - err = k.addTokenToLock(ctx, lock, coin) - if err != nil { - return nil, err + for _, synthlock := range k.GetAllSyntheticLockupsByLockup(ctx, lock.ID) { + k.accumulationStore(ctx, synthlock.SynthDenom).Increase(accumulationKey(synthlock.Duration), tokensToAdd.Amount) } if k.hooks == nil { return lock, nil } +<<<<<<< HEAD k.hooks.OnTokenLocked(ctx, lock.OwnerAddress(), lock.ID, sdk.Coins{coin}, lock.Duration, lock.EndTime) return lock, nil } @@ -181,22 +191,69 @@ func (k Keeper) SlashTokensFromLockByID(ctx sdk.Context, lockID uint64, coins sd } // LockTokens lock tokens from an account for specified duration. +======= + k.hooks.AfterAddTokensToLock(ctx, lock.OwnerAddress(), lock.GetID(), sdk.NewCoins(tokensToAdd)) + + return lock, nil +} + +// CreateLock creates a new lock with the specified duration for the owner. +// Returns an error in the following conditions: +// - account does not have enough balance +>>>>>>> c1155937 (Refactor `lock` method (#1936)) func (k Keeper) CreateLock(ctx sdk.Context, owner sdk.AccAddress, coins sdk.Coins, duration time.Duration) (types.PeriodLock, error) { ID := k.GetLastLockID(ctx) + 1 // unlock time is set at the beginning of unlocking time lock := types.NewPeriodLock(ID, owner, duration, time.Time{}, coins) - err := k.Lock(ctx, lock) + err := k.lock(ctx, lock, lock.Coins) if err != nil { return lock, err } + + // add lock refs into not unlocking queue + err = k.addLockRefs(ctx, lock) + if err != nil { + return lock, err + } + k.SetLastLockID(ctx, lock.ID) return lock, nil } +<<<<<<< HEAD func (k Keeper) clearKeysByPrefix(ctx sdk.Context, prefix []byte) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, prefix) defer iterator.Close() +======= +// lock is an internal utility to lock coins and set corresponding states. +// This is only called by either of the two possible entry points to lock tokens. +// 1. CreateLock +// 2. AddTokensToLockByID +func (k Keeper) lock(ctx sdk.Context, lock types.PeriodLock, tokensToLock sdk.Coins) error { + owner, err := sdk.AccAddressFromBech32(lock.Owner) + if err != nil { + return err + } + if err := k.bk.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, tokensToLock); err != nil { + return err + } + + // store lock object into the store + err = k.setLock(ctx, lock) + if err != nil { + return err + } + + // add to accumulation store + for _, coin := range tokensToLock { + k.accumulationStore(ctx, coin.Denom).Increase(accumulationKey(lock.Duration), coin.Amount) + } + + k.hooks.OnTokenLocked(ctx, owner, lock.ID, lock.Coins, lock.Duration, lock.EndTime) + return nil +} +>>>>>>> c1155937 (Refactor `lock` method (#1936)) for ; iterator.Valid(); iterator.Next() { store.Delete(iterator.Key()) diff --git a/x/lockup/keeper/lock_test.go b/x/lockup/keeper/lock_test.go index 26bdc5bc397..be981bd8520 100644 --- a/x/lockup/keeper/lock_test.go +++ b/x/lockup/keeper/lock_test.go @@ -144,35 +144,6 @@ func (suite *KeeperTestSuite) TestUnlockPeriodLockByID() { suite.Require().Len(locks, 0) } -func (suite *KeeperTestSuite) TestLock() { - // test for coin locking - suite.SetupTest() - - addr1 := sdk.AccAddress([]byte("addr1---------------")) - coins := sdk.Coins{sdk.NewInt64Coin("stake", 10)} - lock := types.NewPeriodLock(1, addr1, time.Second, suite.Ctx.BlockTime().Add(time.Second), coins) - - // try lock without balance - err := suite.App.LockupKeeper.Lock(suite.Ctx, lock) - suite.Require().Error(err) - - // lock with balance - suite.FundAcc(addr1, coins) - err = suite.App.LockupKeeper.Lock(suite.Ctx, lock) - suite.Require().NoError(err) - - // lock with balance with same id - suite.FundAcc(addr1, coins) - err = suite.App.LockupKeeper.Lock(suite.Ctx, lock) - suite.Require().Error(err) - - // lock with balance with different id - lock = types.NewPeriodLock(2, addr1, time.Second, suite.Ctx.BlockTime().Add(time.Second), coins) - suite.FundAcc(addr1, coins) - err = suite.App.LockupKeeper.Lock(suite.Ctx, lock) - suite.Require().NoError(err) -} - func (suite *KeeperTestSuite) TestUnlock() { // test for coin unlocking suite.SetupTest() @@ -180,11 +151,10 @@ func (suite *KeeperTestSuite) TestUnlock() { addr1 := sdk.AccAddress([]byte("addr1---------------")) coins := sdk.Coins{sdk.NewInt64Coin("stake", 10)} - lock := types.NewPeriodLock(1, addr1, time.Second, time.Time{}, coins) // lock with balance suite.FundAcc(addr1, coins) - err := suite.App.LockupKeeper.Lock(suite.Ctx, lock) + lock, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, addr1, coins, time.Second) suite.Require().NoError(err) // begin unlock with lock object @@ -320,6 +290,65 @@ func (suite *KeeperTestSuite) TestLocksLongerThanDurationDenom() { suite.Require().Len(locks, 1) } +func (suite *KeeperTestSuite) TestCreateLock() { + suite.SetupTest() + + addr1 := sdk.AccAddress([]byte("addr1---------------")) + coins := sdk.Coins{sdk.NewInt64Coin("stake", 10)} + + // test locking without balance + _, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, addr1, coins, time.Second) + suite.Require().Error(err) + + suite.FundAcc(addr1, coins) + + lock, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, addr1, coins, time.Second) + suite.Require().NoError(err) + + // check new lock + suite.Require().Equal(coins, lock.Coins) + suite.Require().Equal(time.Second, lock.Duration) + suite.Require().Equal(time.Time{}, lock.EndTime) + suite.Require().Equal(uint64(1), lock.ID) + + lockID := suite.App.LockupKeeper.GetLastLockID(suite.Ctx) + suite.Require().Equal(uint64(1), lockID) + + // check accumulation store + accum := suite.App.LockupKeeper.GetPeriodLocksAccumulation(suite.Ctx, types.QueryCondition{ + LockQueryType: types.ByDuration, + Denom: "stake", + Duration: time.Second, + }) + suite.Require().Equal(accum.String(), "10") + + // create new lock + coins = sdk.Coins{sdk.NewInt64Coin("stake", 20)} + suite.FundAcc(addr1, coins) + + lock, err = suite.App.LockupKeeper.CreateLock(suite.Ctx, addr1, coins, time.Second) + suite.Require().NoError(err) + + lockID = suite.App.LockupKeeper.GetLastLockID(suite.Ctx) + suite.Require().Equal(uint64(2), lockID) + + // check accumulation store + accum = suite.App.LockupKeeper.GetPeriodLocksAccumulation(suite.Ctx, types.QueryCondition{ + LockQueryType: types.ByDuration, + Denom: "stake", + Duration: time.Second, + }) + suite.Require().Equal(accum.String(), "30") + + // check balance + balance := suite.App.BankKeeper.GetBalance(suite.Ctx, addr1, "stake") + suite.Require().Equal(sdk.ZeroInt(), balance.Amount) + + acc := suite.App.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) + balance = suite.App.BankKeeper.GetBalance(suite.Ctx, acc.GetAddress(), "stake") + suite.Require().Equal(sdk.NewInt(30), balance.Amount) +} + func (suite *KeeperTestSuite) TestAddTokensToLock() { suite.SetupTest() @@ -381,6 +410,52 @@ func (suite *KeeperTestSuite) TestAddTokensToLock() { suite.Require().Error(err) } +func (suite *KeeperTestSuite) TestLock() { + suite.SetupTest() + + addr1 := sdk.AccAddress([]byte("addr1---------------")) + coins := sdk.Coins{sdk.NewInt64Coin("stake", 10)} + + lock := types.PeriodLock{ + ID: 1, + Owner: addr1.String(), + Duration: time.Second, + EndTime: time.Time{}, + Coins: coins, + } + + // test locking without balance + err := suite.App.LockupKeeper.Lock(suite.Ctx, lock, coins) + suite.Require().Error(err) + + // check accumulation store + accum := suite.App.LockupKeeper.GetPeriodLocksAccumulation(suite.Ctx, types.QueryCondition{ + LockQueryType: types.ByDuration, + Denom: "stake", + Duration: time.Second, + }) + suite.Require().Equal(accum.String(), "0") + + suite.FundAcc(addr1, coins) + err = suite.App.LockupKeeper.Lock(suite.Ctx, lock, coins) + suite.Require().NoError(err) + + // check accumulation store + accum = suite.App.LockupKeeper.GetPeriodLocksAccumulation(suite.Ctx, types.QueryCondition{ + LockQueryType: types.ByDuration, + Denom: "stake", + Duration: time.Second, + }) + suite.Require().Equal(accum.String(), "10") + + balance := suite.App.BankKeeper.GetBalance(suite.Ctx, addr1, "stake") + suite.Require().Equal(sdk.ZeroInt(), balance.Amount) + + acc := suite.App.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) + balance = suite.App.BankKeeper.GetBalance(suite.Ctx, acc.GetAddress(), "stake") + suite.Require().Equal(sdk.NewInt(10), balance.Amount) +} + func (suite *KeeperTestSuite) AddTokensToLockForSynth() { suite.SetupTest() From 0922c7a5b60cf460344b88a47a2eccdf4f538b73 Mon Sep 17 00:00:00 2001 From: mattverse Date: Tue, 12 Jul 2022 17:25:03 +0900 Subject: [PATCH 2/3] Fix merge conflict --- x/lockup/keeper/admin_keeper_test.go | 5 - x/lockup/keeper/iterator.go | 2 +- x/lockup/keeper/lock.go | 681 ++++++++++++--------------- x/lockup/keeper/lock_test.go | 10 +- x/lockup/keeper/synthetic_lock.go | 100 ++-- 5 files changed, 378 insertions(+), 420 deletions(-) diff --git a/x/lockup/keeper/admin_keeper_test.go b/x/lockup/keeper/admin_keeper_test.go index 565a0dc6959..84a4af63362 100644 --- a/x/lockup/keeper/admin_keeper_test.go +++ b/x/lockup/keeper/admin_keeper_test.go @@ -3,12 +3,7 @@ package keeper_test import ( "time" -<<<<<<< HEAD "github.com/osmosis-labs/osmosis/v10/x/lockup/keeper" - "github.com/osmosis-labs/osmosis/v10/x/lockup/types" -======= - "github.com/osmosis-labs/osmosis/v7/x/lockup/keeper" ->>>>>>> c1155937 (Refactor `lock` method (#1936)) sdk "github.com/cosmos/cosmos-sdk/types" ) diff --git a/x/lockup/keeper/iterator.go b/x/lockup/keeper/iterator.go index 7e622b1ffc8..750e117e4ed 100644 --- a/x/lockup/keeper/iterator.go +++ b/x/lockup/keeper/iterator.go @@ -194,7 +194,7 @@ func (k Keeper) unlockFromIterator(ctx sdk.Context, iterator db.Iterator) ([]typ coins := sdk.Coins{} locks := k.getLocksFromIterator(ctx, iterator) for _, lock := range locks { - err := k.Unlock(ctx, lock.ID) + err := k.UnlockMaturedLock(ctx, lock.ID) if err != nil { panic(err) } diff --git a/x/lockup/keeper/lock.go b/x/lockup/keeper/lock.go index 540fb33fe6a..2ac861bda89 100644 --- a/x/lockup/keeper/lock.go +++ b/x/lockup/keeper/lock.go @@ -14,29 +14,14 @@ import ( "github.com/osmosis-labs/osmosis/v10/x/lockup/types" ) -// TODO: Reorganize functions in this file - // WithdrawAllMaturedLocks withdraws every lock thats in the process of unlocking, and has finished unlocking by // the current block time. func (k Keeper) WithdrawAllMaturedLocks(ctx sdk.Context) { k.unlockFromIterator(ctx, k.LockIteratorBeforeTime(ctx, ctx.BlockTime())) } -func (k Keeper) getCoinsFromLocks(locks []types.PeriodLock) sdk.Coins { - coins := sdk.Coins{} - for _, lock := range locks { - coins = coins.Add(lock.Coins...) - } - return coins -} - -func (k Keeper) accumulationStore(ctx sdk.Context, denom string) store.Tree { - return store.NewTree(prefix.NewStore(ctx.KVStore(k.storeKey), accumulationStorePrefix(denom)), 10) -} - -// GetModuleBalance Returns full balance of the module. +// GetModuleBalance returns full balance of the module. func (k Keeper) GetModuleBalance(ctx sdk.Context) sdk.Coins { - // TODO: should add invariant test for module balance and lock items acc := k.ak.GetModuleAccount(ctx, types.ModuleName) return k.bk.GetAllBalances(ctx, acc.GetAddress()) } @@ -56,62 +41,14 @@ func (k Keeper) GetPeriodLocksAccumulation(ctx sdk.Context, query types.QueryCon return k.accumulationStore(ctx, query.Denom).SubsetAccumulation(beginKey, nil) } -// BeginUnlockAllNotUnlockings begins unlock for all not unlocking coins. +// BeginUnlockAllNotUnlockings begins unlock for all not unlocking locks of the given account. func (k Keeper) BeginUnlockAllNotUnlockings(ctx sdk.Context, account sdk.AccAddress) ([]types.PeriodLock, error) { locks, err := k.beginUnlockFromIterator(ctx, k.AccountLockIterator(ctx, false, account)) return locks, err } -func (k Keeper) addTokenToLock(ctx sdk.Context, lock *types.PeriodLock, coin sdk.Coin) error { - lock.Coins = lock.Coins.Add(coin) - - err := k.setLock(ctx, *lock) - if err != nil { - return err - } - - // modifications to accumulation store - k.accumulationStore(ctx, coin.Denom).Increase(accumulationKey(lock.Duration), coin.Amount) - // modifications to accumulation store by synthlocks - // CONTRACT: lock will have synthetic lock only if it has a single coin - lockedCoin, err := lock.SingleCoin() - if err == nil { - for _, synthlock := range k.GetAllSyntheticLockupsByLockup(ctx, lock.ID) { - k.accumulationStore(ctx, synthlock.SynthDenom).Increase(accumulationKey(synthlock.Duration), sdk.NewCoins(coin).AmountOf(lockedCoin.Denom)) - } - } - - k.hooks.AfterAddTokensToLock(ctx, lock.OwnerAddress(), lock.GetID(), sdk.NewCoins(coin)) - - return nil -} - -// removeTokensFromLock is called by lockup slash function - called by superfluid module only. -func (k Keeper) removeTokensFromLock(ctx sdk.Context, lock *types.PeriodLock, coins sdk.Coins) error { - // TODO: Handle 100% slash eventually, not needed for osmosis codebase atm. - lock.Coins = lock.Coins.Sub(coins) - - err := k.setLock(ctx, *lock) - if err != nil { - return err - } - - // modifications to accumulation store - for _, coin := range coins { - k.accumulationStore(ctx, coin.Denom).Decrease(accumulationKey(lock.Duration), coin.Amount) - } - - // increase synthetic lockup's accumulation store - synthLocks := k.GetAllSyntheticLockupsByLockup(ctx, lock.ID) - - // Note: since synthetic lockup deletion is using native lockup's coins to reduce accumulation store - // all the synthetic lockups' accumulation should be decreased - for _, synthlock := range synthLocks { - k.accumulationStore(ctx, synthlock.SynthDenom).Decrease(accumulationKey(synthlock.Duration), coins[0].Amount) - } - return nil -} - +// AddToExistingLock adds the given coin to the existing lock with the same owner and duration. +// Returns an empty array of period lock when a lock with the given condition does not exist. func (k Keeper) AddToExistingLock(ctx sdk.Context, owner sdk.AccAddress, coin sdk.Coin, duration time.Duration) ([]types.PeriodLock, error) { locks := k.GetAccountLockedDurationNotUnlockingOnly(ctx, owner, coin.Denom, duration) // if existing lock with same duration and denom exists, just add there @@ -125,17 +62,11 @@ func (k Keeper) AddToExistingLock(ctx sdk.Context, owner sdk.AccAddress, coin sd return locks, nil } -<<<<<<< HEAD -// AddTokensToLock locks more tokens into a lockup -// This also saves the lock to the store. -func (k Keeper) AddTokensToLockByID(ctx sdk.Context, lockID uint64, owner sdk.AccAddress, coin sdk.Coin) (*types.PeriodLock, error) { -======= // AddTokensToLock locks additional tokens into an existing lock with the given ID. // Tokens locked are sent and kept in the module account. // This method alters the lock state in store, thus we do a sanity check to ensure // lock owner matches the given owner. func (k Keeper) AddTokensToLockByID(ctx sdk.Context, lockID uint64, owner sdk.AccAddress, tokensToAdd sdk.Coin) (*types.PeriodLock, error) { ->>>>>>> c1155937 (Refactor `lock` method (#1936)) lock, err := k.GetLockByID(ctx, lockID) if err != nil { return nil, err @@ -159,39 +90,6 @@ func (k Keeper) AddTokensToLockByID(ctx sdk.Context, lockID uint64, owner sdk.Ac return lock, nil } -<<<<<<< HEAD - k.hooks.OnTokenLocked(ctx, lock.OwnerAddress(), lock.ID, sdk.Coins{coin}, lock.Duration, lock.EndTime) - return lock, nil -} - -// SlashTokensFromLockByID send slashed tokens to community pool - called by superfluid module only. -func (k Keeper) SlashTokensFromLockByID(ctx sdk.Context, lockID uint64, coins sdk.Coins) (*types.PeriodLock, error) { - lock, err := k.GetLockByID(ctx, lockID) - if err != nil { - return nil, err - } - - modAddr := k.ak.GetModuleAddress(types.ModuleName) - err = k.dk.FundCommunityPool(ctx, coins, modAddr) - if err != nil { - return nil, err - } - - err = k.removeTokensFromLock(ctx, lock, coins) - if err != nil { - return nil, err - } - - if k.hooks == nil { - return lock, nil - } - - k.hooks.OnTokenSlashed(ctx, lock.ID, coins) - return lock, nil -} - -// LockTokens lock tokens from an account for specified duration. -======= k.hooks.AfterAddTokensToLock(ctx, lock.OwnerAddress(), lock.GetID(), sdk.NewCoins(tokensToAdd)) return lock, nil @@ -200,10 +98,10 @@ func (k Keeper) SlashTokensFromLockByID(ctx sdk.Context, lockID uint64, coins sd // CreateLock creates a new lock with the specified duration for the owner. // Returns an error in the following conditions: // - account does not have enough balance ->>>>>>> c1155937 (Refactor `lock` method (#1936)) func (k Keeper) CreateLock(ctx sdk.Context, owner sdk.AccAddress, coins sdk.Coins, duration time.Duration) (types.PeriodLock, error) { ID := k.GetLastLockID(ctx) + 1 - // unlock time is set at the beginning of unlocking time + // unlock time is initially set without a value, gets set as unlock start time + duration + // when unlocking starts. lock := types.NewPeriodLock(ID, owner, duration, time.Time{}, coins) err := k.lock(ctx, lock, lock.Coins) if err != nil { @@ -220,12 +118,6 @@ func (k Keeper) CreateLock(ctx sdk.Context, owner sdk.AccAddress, coins sdk.Coin return lock, nil } -<<<<<<< HEAD -func (k Keeper) clearKeysByPrefix(ctx sdk.Context, prefix []byte) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, prefix) - defer iterator.Close() -======= // lock is an internal utility to lock coins and set corresponding states. // This is only called by either of the two possible entry points to lock tokens. // 1. CreateLock @@ -253,238 +145,9 @@ func (k Keeper) lock(ctx sdk.Context, lock types.PeriodLock, tokensToLock sdk.Co k.hooks.OnTokenLocked(ctx, owner, lock.ID, lock.Coins, lock.Duration, lock.EndTime) return nil } ->>>>>>> c1155937 (Refactor `lock` method (#1936)) - - for ; iterator.Valid(); iterator.Next() { - store.Delete(iterator.Key()) - } -} - -func (k Keeper) ClearAccumulationStores(ctx sdk.Context) { - k.clearKeysByPrefix(ctx, types.KeyPrefixLockAccumulation) -} - -// ResetAllLocks takes a set of locks, and initializes state to be storing -// them all correctly. This utilizes batch optimizations to improve efficiency, -// as this becomes a bottleneck at chain initialization & upgrades. -func (k Keeper) ResetAllLocks(ctx sdk.Context, locks []types.PeriodLock) error { - // index by coin.Denom, them duration -> amt - // We accumulate the accumulation store entries separately, - // to avoid hitting the myriad of slowdowns in the SDK iterator creation process. - // We then save these once to the accumulation store at the end. - accumulationStoreEntries := make(map[string]map[time.Duration]sdk.Int) - denoms := []string{} - for i, lock := range locks { - if i%25000 == 0 { - msg := fmt.Sprintf("Reset %d lock refs, cur lock ID %d", i, lock.ID) - ctx.Logger().Info(msg) - } - err := k.setLockAndResetLockRefs(ctx, lock) - if err != nil { - return err - } - - // Add to the accumlation store cache - for _, coin := range lock.Coins { - // update or create the new map from duration -> Int for this denom. - var curDurationMap map[time.Duration]sdk.Int - if durationMap, ok := accumulationStoreEntries[coin.Denom]; ok { - curDurationMap = durationMap - // update or create new amount in the duration map - newAmt := coin.Amount - if curAmt, ok := durationMap[lock.Duration]; ok { - newAmt = newAmt.Add(curAmt) - } - curDurationMap[lock.Duration] = newAmt - } else { - denoms = append(denoms, coin.Denom) - curDurationMap = map[time.Duration]sdk.Int{lock.Duration: coin.Amount} - } - accumulationStoreEntries[coin.Denom] = curDurationMap - } - } - - // deterministically iterate over durationMap cache. - sort.Strings(denoms) - for _, denom := range denoms { - curDurationMap := accumulationStoreEntries[denom] - durations := make([]time.Duration, 0, len(curDurationMap)) - for duration := range curDurationMap { - durations = append(durations, duration) - } - sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] }) - // now that we have a sorted list of durations for this denom, - // add them all to accumulation store - msg := fmt.Sprintf("Setting accumulation entries for locks for %s, there are %d distinct durations", - denom, len(durations)) - ctx.Logger().Info(msg) - for _, d := range durations { - amt := curDurationMap[d] - k.accumulationStore(ctx, denom).Increase(accumulationKey(d), amt) - } - } - - return nil -} - -func (k Keeper) ResetAllSyntheticLocks(ctx sdk.Context, syntheticLocks []types.SyntheticLock) error { - // index by coin.Denom, them duration -> amt - // We accumulate the accumulation store entries separately, - // to avoid hitting the myriad of slowdowns in the SDK iterator creation process. - // We then save these once to the accumulation store at the end. - accumulationStoreEntries := make(map[string]map[time.Duration]sdk.Int) - denoms := []string{} - for i, synthLock := range syntheticLocks { - if i%25000 == 0 { - msg := fmt.Sprintf("Reset %d synthetic lock refs", i) - ctx.Logger().Info(msg) - } - - // Add to the accumlation store cache - lock, err := k.GetLockByID(ctx, synthLock.UnderlyingLockId) - if err != nil { - return err - } - - err = k.setSyntheticLockAndResetRefs(ctx, *lock, synthLock) - if err != nil { - return err - } - - coin, err := lock.SingleCoin() - if err != nil { - return err - } - - var curDurationMap map[time.Duration]sdk.Int - if durationMap, ok := accumulationStoreEntries[synthLock.SynthDenom]; ok { - curDurationMap = durationMap - newAmt := coin.Amount - if curAmt, ok := durationMap[synthLock.Duration]; ok { - newAmt = newAmt.Add(curAmt) - } - curDurationMap[synthLock.Duration] = newAmt - } else { - denoms = append(denoms, synthLock.SynthDenom) - curDurationMap = map[time.Duration]sdk.Int{synthLock.Duration: coin.Amount} - } - accumulationStoreEntries[synthLock.SynthDenom] = curDurationMap - } - - // deterministically iterate over durationMap cache. - sort.Strings(denoms) - for _, denom := range denoms { - curDurationMap := accumulationStoreEntries[denom] - durations := make([]time.Duration, 0, len(curDurationMap)) - for duration := range curDurationMap { - durations = append(durations, duration) - } - sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] }) - // now that we have a sorted list of durations for this denom, - // add them all to accumulation store - msg := fmt.Sprintf("Setting accumulation entries for locks for %s, there are %d distinct durations", - denom, len(durations)) - ctx.Logger().Info(msg) - for _, d := range durations { - amt := curDurationMap[d] - k.accumulationStore(ctx, denom).Increase(accumulationKey(d), amt) - } - } - - return nil -} - -func (k Keeper) setSyntheticLockAndResetRefs(ctx sdk.Context, lock types.PeriodLock, synthLock types.SyntheticLock) error { - err := k.setSyntheticLockupObject(ctx, &synthLock) - if err != nil { - return err - } - - // store synth lock refs - return k.addSyntheticLockRefs(ctx, lock, synthLock) -} - -// setLockAndResetLockRefs sets the lock, and resets all of its lock references -// This puts the lock into a 'clean' state, aside from the AccumulationStore. -func (k Keeper) setLockAndResetLockRefs(ctx sdk.Context, lock types.PeriodLock) error { - err := k.setLock(ctx, lock) - if err != nil { - return err - } - - return k.addLockRefs(ctx, lock) -} - -// setLock is a utility to store lock object into the store. -func (k Keeper) setLock(ctx sdk.Context, lock types.PeriodLock) error { - store := ctx.KVStore(k.storeKey) - bz, err := proto.Marshal(&lock) - if err != nil { - return err - } - store.Set(lockStoreKey(lock.ID), bz) - return nil -} - -// deleteLock removes the lock object from the state. -func (k Keeper) deleteLock(ctx sdk.Context, id uint64) { - store := ctx.KVStore(k.storeKey) - store.Delete(lockStoreKey(id)) -} - -// Lock is a utility to lock coins into module account. -func (k Keeper) Lock(ctx sdk.Context, lock types.PeriodLock) error { - owner, err := sdk.AccAddressFromBech32(lock.Owner) - if err != nil { - return err - } - if err := k.bk.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, lock.Coins); err != nil { - return err - } - - // store lock object into the store - store := ctx.KVStore(k.storeKey) - bz, err := proto.Marshal(&lock) - if err != nil { - return err - } - store.Set(lockStoreKey(lock.ID), bz) - - // add lock refs into not unlocking queue - err = k.addLockRefs(ctx, lock) - if err != nil { - return err - } - - // add to accumulation store - for _, coin := range lock.Coins { - k.accumulationStore(ctx, coin.Denom).Increase(accumulationKey(lock.Duration), coin.Amount) - } - - k.hooks.OnTokenLocked(ctx, owner, lock.ID, lock.Coins, lock.Duration, lock.EndTime) - return nil -} - -// splitLock splits a lock with the given amount, and stores split new lock to the state. -func (k Keeper) splitLock(ctx sdk.Context, lock types.PeriodLock, coins sdk.Coins) (types.PeriodLock, error) { - if lock.IsUnlocking() { - return types.PeriodLock{}, fmt.Errorf("cannot split unlocking lock") - } - lock.Coins = lock.Coins.Sub(coins) - err := k.setLock(ctx, lock) - if err != nil { - return types.PeriodLock{}, err - } - - splitLockID := k.GetLastLockID(ctx) + 1 - k.SetLastLockID(ctx, splitLockID) - - splitLock := types.NewPeriodLock(splitLockID, lock.OwnerAddress(), lock.Duration, lock.EndTime, coins) - err = k.setLock(ctx, splitLock) - return splitLock, err -} // BeginUnlock is a utility to start unlocking coins from NotUnlocking queue. +// Returns an error if the lock has a synthetic lock. func (k Keeper) BeginUnlock(ctx sdk.Context, lockID uint64, coins sdk.Coins) error { // prohibit BeginUnlock if synthetic locks are referring to this // TODO: In the future, make synthetic locks only get partial restrictions on the main lock. @@ -496,18 +159,26 @@ func (k Keeper) BeginUnlock(ctx sdk.Context, lockID uint64, coins sdk.Coins) err return fmt.Errorf("cannot BeginUnlocking a lock with synthetic lockup") } - return k.beginForceUnlock(ctx, *lock, coins) + return k.beginUnlock(ctx, *lock, coins) } +// BeginForceUnlock begins force unlock of the given lock. +// This method should be called by the superfluid module ONLY, as it does not check whether +// the lock has a synthetic lock or not before unlocking. func (k Keeper) BeginForceUnlock(ctx sdk.Context, lockID uint64, coins sdk.Coins) error { lock, err := k.GetLockByID(ctx, lockID) if err != nil { return err } - return k.beginForceUnlock(ctx, *lock, coins) + return k.beginUnlock(ctx, *lock, coins) } -func (k Keeper) beginForceUnlock(ctx sdk.Context, lock types.PeriodLock, coins sdk.Coins) error { +// beginUnlock unlocks specified tokens from the given lock. Existing lock refs +// of not unlocking queue are deleted and new lock refs are then added. +// EndTime of the lock is set within this method. +// Coins provided as the parameter does not require to have all the tokens in the lock, +// as we allow partial unlockings of a lock. +func (k Keeper) beginUnlock(ctx sdk.Context, lock types.PeriodLock, coins sdk.Coins) error { // sanity check if !coins.IsAllLTE(lock.Coins) { return fmt.Errorf("requested amount to unlock exceeds locked tokens") @@ -524,13 +195,13 @@ func (k Keeper) beginForceUnlock(ctx sdk.Context, lock types.PeriodLock, coins s lock = splitLock } - // remove lock refs from not unlocking queue if exists + // remove existing lock refs from not unlocking queue err := k.deleteLockRefs(ctx, types.KeyPrefixNotUnlocking, lock) if err != nil { return err } - // store lock with end time set + // store lock with the end time set to current block time + duration lock.EndTime = ctx.BlockTime().Add(lock.Duration) err = k.setLock(ctx, lock) if err != nil { @@ -550,6 +221,20 @@ func (k Keeper) beginForceUnlock(ctx sdk.Context, lock types.PeriodLock, coins s return nil } +func (k Keeper) clearKeysByPrefix(ctx sdk.Context, prefix []byte) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, prefix) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + store.Delete(iterator.Key()) + } +} + +func (k Keeper) ClearAccumulationStores(ctx sdk.Context) { + k.clearKeysByPrefix(ctx, types.KeyPrefixLockAccumulation) +} + func (k Keeper) BeginForceUnlockWithEndTime(ctx sdk.Context, lockID uint64, endTime time.Time) error { lock, err := k.GetLockByID(ctx, lockID) if err != nil { @@ -585,8 +270,9 @@ func (k Keeper) beginForceUnlockWithEndTime(ctx sdk.Context, lock types.PeriodLo return nil } -// Unlock is a utility to unlock coins from module account. -func (k Keeper) Unlock(ctx sdk.Context, lockID uint64) error { +// UnlockMaturedLock finishes unlocking by sending back the locked tokens from the module accounts +// to the owner. This method requires lock to be matured, having passed the endtime of the lock. +func (k Keeper) UnlockMaturedLock(ctx sdk.Context, lockID uint64) error { lock, err := k.GetLockByID(ctx, lockID) if err != nil { return err @@ -601,10 +287,10 @@ func (k Keeper) Unlock(ctx sdk.Context, lockID uint64) error { return fmt.Errorf("lock is not unlockable yet: %s >= %s", curTime.String(), lock.EndTime.String()) } - return k.unlockInternalLogic(ctx, *lock) + return k.unlockMaturedLockInternalLogic(ctx, *lock) } -// ForceUnlock ignores unlock duration and immediately unlocks the lock and refunds tokens to lock owner.. +// 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) @@ -612,7 +298,7 @@ func (k Keeper) ForceUnlock(ctx sdk.Context, lock types.PeriodLock) error { // 3) Run logic to delete unlocking metadata, and send tokens to owner. synthLocks := k.GetAllSyntheticLockupsByLockup(ctx, lock.ID) - err := k.BreakAllSyntheticLocks(ctx, lock, synthLocks) + err := k.DeleteAllSyntheticLocks(ctx, lock, synthLocks) if err != nil { return err } @@ -629,32 +315,11 @@ func (k Keeper) ForceUnlock(ctx sdk.Context, lock types.PeriodLock) error { if err != nil { return err } - return k.unlockInternalLogic(ctx, *lockPtr) + return k.unlockMaturedLockInternalLogic(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 { +// unlockMaturedLockInternalLogic handles internal logic for finishing unlocking matured locks. +func (k Keeper) unlockMaturedLockInternalLogic(ctx sdk.Context, lock types.PeriodLock) error { owner, err := sdk.AccAddressFromBech32(lock.Owner) if err != nil { return err @@ -682,6 +347,12 @@ func (k Keeper) unlockInternalLogic(ctx sdk.Context, lock types.PeriodLock) erro return nil } +// ExtendLockup changes the existing lock duration to the given lock duration. +// Updating lock duration would fail on either of the following conditions. +// 1. Only lock owner is able to change the duration of the lock. +// 2. Locks that are unlokcing are not allowed to change duration. +// 3. Locks that have synthetic lockup are not allowed to change. +// 4. Provided duration should be greater than the original duration. func (k Keeper) ExtendLockup(ctx sdk.Context, lock types.PeriodLock, newDuration time.Duration) error { if lock.IsUnlocking() { return fmt.Errorf("cannot edit unlocking lockup for lock %d", lock.ID) @@ -732,3 +403,257 @@ func (k Keeper) ExtendLockup(ctx sdk.Context, lock types.PeriodLock, newDuration return nil } + +// ResetAllLocks takes a set of locks, and initializes state to be storing +// them all correctly. This utilizes batch optimizations to improve efficiency, +// as this becomes a bottleneck at chain initialization & upgrades. +func (k Keeper) ResetAllLocks(ctx sdk.Context, locks []types.PeriodLock) error { + // index by coin.Denom, them duration -> amt + // We accumulate the accumulation store entries separately, + // to avoid hitting the myriad of slowdowns in the SDK iterator creation process. + // We then save these once to the accumulation store at the end. + accumulationStoreEntries := make(map[string]map[time.Duration]sdk.Int) + denoms := []string{} + for i, lock := range locks { + if i%25000 == 0 { + msg := fmt.Sprintf("Reset %d lock refs, cur lock ID %d", i, lock.ID) + ctx.Logger().Info(msg) + } + err := k.setLockAndResetLockRefs(ctx, lock) + if err != nil { + return err + } + + // Add to the accumlation store cache + for _, coin := range lock.Coins { + // update or create the new map from duration -> Int for this denom. + var curDurationMap map[time.Duration]sdk.Int + if durationMap, ok := accumulationStoreEntries[coin.Denom]; ok { + curDurationMap = durationMap + // update or create new amount in the duration map + newAmt := coin.Amount + if curAmt, ok := durationMap[lock.Duration]; ok { + newAmt = newAmt.Add(curAmt) + } + curDurationMap[lock.Duration] = newAmt + } else { + denoms = append(denoms, coin.Denom) + curDurationMap = map[time.Duration]sdk.Int{lock.Duration: coin.Amount} + } + accumulationStoreEntries[coin.Denom] = curDurationMap + } + } + + // deterministically iterate over durationMap cache. + sort.Strings(denoms) + for _, denom := range denoms { + curDurationMap := accumulationStoreEntries[denom] + durations := make([]time.Duration, 0, len(curDurationMap)) + for duration := range curDurationMap { + durations = append(durations, duration) + } + sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] }) + // now that we have a sorted list of durations for this denom, + // add them all to accumulation store + msg := fmt.Sprintf("Setting accumulation entries for locks for %s, there are %d distinct durations", + denom, len(durations)) + ctx.Logger().Info(msg) + for _, d := range durations { + amt := curDurationMap[d] + k.accumulationStore(ctx, denom).Increase(accumulationKey(d), amt) + } + } + + return nil +} + +func (k Keeper) ResetAllSyntheticLocks(ctx sdk.Context, syntheticLocks []types.SyntheticLock) error { + // index by coin.Denom, them duration -> amt + // We accumulate the accumulation store entries separately, + // to avoid hitting the myriad of slowdowns in the SDK iterator creation process. + // We then save these once to the accumulation store at the end. + accumulationStoreEntries := make(map[string]map[time.Duration]sdk.Int) + denoms := []string{} + for i, synthLock := range syntheticLocks { + if i%25000 == 0 { + msg := fmt.Sprintf("Reset %d synthetic lock refs", i) + ctx.Logger().Info(msg) + } + + // Add to the accumlation store cache + lock, err := k.GetLockByID(ctx, synthLock.UnderlyingLockId) + if err != nil { + return err + } + + err = k.setSyntheticLockAndResetRefs(ctx, *lock, synthLock) + if err != nil { + return err + } + + coin, err := lock.SingleCoin() + if err != nil { + return err + } + + var curDurationMap map[time.Duration]sdk.Int + if durationMap, ok := accumulationStoreEntries[synthLock.SynthDenom]; ok { + curDurationMap = durationMap + newAmt := coin.Amount + if curAmt, ok := durationMap[synthLock.Duration]; ok { + newAmt = newAmt.Add(curAmt) + } + curDurationMap[synthLock.Duration] = newAmt + } else { + denoms = append(denoms, synthLock.SynthDenom) + curDurationMap = map[time.Duration]sdk.Int{synthLock.Duration: coin.Amount} + } + accumulationStoreEntries[synthLock.SynthDenom] = curDurationMap + } + + // deterministically iterate over durationMap cache. + sort.Strings(denoms) + for _, denom := range denoms { + curDurationMap := accumulationStoreEntries[denom] + durations := make([]time.Duration, 0, len(curDurationMap)) + for duration := range curDurationMap { + durations = append(durations, duration) + } + sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] }) + // now that we have a sorted list of durations for this denom, + // add them all to accumulation store + msg := fmt.Sprintf("Setting accumulation entries for locks for %s, there are %d distinct durations", + denom, len(durations)) + ctx.Logger().Info(msg) + for _, d := range durations { + amt := curDurationMap[d] + k.accumulationStore(ctx, denom).Increase(accumulationKey(d), amt) + } + } + + return nil +} + +// SlashTokensFromLockByID sends slashed tokens directly from the lock to the community pool. +// Called by the superfluid module ONLY. +func (k Keeper) SlashTokensFromLockByID(ctx sdk.Context, lockID uint64, coins sdk.Coins) (*types.PeriodLock, error) { + lock, err := k.GetLockByID(ctx, lockID) + if err != nil { + return nil, err + } + + modAddr := k.ak.GetModuleAddress(types.ModuleName) + err = k.dk.FundCommunityPool(ctx, coins, modAddr) + if err != nil { + return nil, err + } + + err = k.removeTokensFromLock(ctx, lock, coins) + if err != nil { + return nil, err + } + + if k.hooks == nil { + return lock, nil + } + + k.hooks.OnTokenSlashed(ctx, lock.ID, coins) + return lock, nil +} + +func (k Keeper) accumulationStore(ctx sdk.Context, denom string) store.Tree { + return store.NewTree(prefix.NewStore(ctx.KVStore(k.storeKey), accumulationStorePrefix(denom)), 10) +} + +// removeTokensFromLock is called by lockup slash function. +// Called by the superfluid module ONLY. +func (k Keeper) removeTokensFromLock(ctx sdk.Context, lock *types.PeriodLock, coins sdk.Coins) error { + // TODO: Handle 100% slash eventually, not needed for osmosis codebase atm. + lock.Coins = lock.Coins.Sub(coins) + + err := k.setLock(ctx, *lock) + if err != nil { + return err + } + + // modifications to accumulation store + for _, coin := range coins { + k.accumulationStore(ctx, coin.Denom).Decrease(accumulationKey(lock.Duration), coin.Amount) + } + + // increase synthetic lockup's accumulation store + synthLocks := k.GetAllSyntheticLockupsByLockup(ctx, lock.ID) + + // Note: since synthetic lockup deletion is using native lockup's coins to reduce accumulation store + // all the synthetic lockups' accumulation should be decreased + for _, synthlock := range synthLocks { + k.accumulationStore(ctx, synthlock.SynthDenom).Decrease(accumulationKey(synthlock.Duration), coins[0].Amount) + } + return nil +} + +// setLock is a utility to store lock object into the store. +func (k Keeper) setLock(ctx sdk.Context, lock types.PeriodLock) error { + store := ctx.KVStore(k.storeKey) + bz, err := proto.Marshal(&lock) + if err != nil { + return err + } + store.Set(lockStoreKey(lock.ID), bz) + return nil +} + +// setLockAndResetLockRefs sets the lock, and resets all of its lock references +// This puts the lock into a 'clean' state, aside from the AccumulationStore. +func (k Keeper) setLockAndResetLockRefs(ctx sdk.Context, lock types.PeriodLock) error { + err := k.setLock(ctx, lock) + if err != nil { + return err + } + + return k.addLockRefs(ctx, lock) +} + +// setSyntheticLockAndResetRefs sets the synthetic lock object, and resets all of its lock references +func (k Keeper) setSyntheticLockAndResetRefs(ctx sdk.Context, lock types.PeriodLock, synthLock types.SyntheticLock) error { + err := k.setSyntheticLockupObject(ctx, &synthLock) + if err != nil { + return err + } + + // store synth lock refs + return k.addSyntheticLockRefs(ctx, lock, synthLock) +} + +// deleteLock removes the lock object from the state. +func (k Keeper) deleteLock(ctx sdk.Context, id uint64) { + store := ctx.KVStore(k.storeKey) + store.Delete(lockStoreKey(id)) +} + +// splitLock splits a lock with the given amount, and stores split new lock to the state. +func (k Keeper) splitLock(ctx sdk.Context, lock types.PeriodLock, coins sdk.Coins) (types.PeriodLock, error) { + if lock.IsUnlocking() { + return types.PeriodLock{}, fmt.Errorf("cannot split unlocking lock") + } + lock.Coins = lock.Coins.Sub(coins) + err := k.setLock(ctx, lock) + if err != nil { + return types.PeriodLock{}, err + } + + splitLockID := k.GetLastLockID(ctx) + 1 + k.SetLastLockID(ctx, splitLockID) + + splitLock := types.NewPeriodLock(splitLockID, lock.OwnerAddress(), lock.Duration, lock.EndTime, coins) + err = k.setLock(ctx, splitLock) + return splitLock, err +} + +func (k Keeper) getCoinsFromLocks(locks []types.PeriodLock) sdk.Coins { + coins := sdk.Coins{} + for _, lock := range locks { + coins = coins.Add(lock.Coins...) + } + return coins +} diff --git a/x/lockup/keeper/lock_test.go b/x/lockup/keeper/lock_test.go index be981bd8520..854b024dde4 100644 --- a/x/lockup/keeper/lock_test.go +++ b/x/lockup/keeper/lock_test.go @@ -114,7 +114,7 @@ func (suite *KeeperTestSuite) TestUnlockPeriodLockByID() { // unlock lock just now lock, err := lockKeeper.GetLockByID(suite.Ctx, 1) suite.Require().NoError(err) - err = lockKeeper.Unlock(suite.Ctx, lock.ID) + err = lockKeeper.UnlockMaturedLock(suite.Ctx, lock.ID) suite.Require().Error(err) // move start time to 1 second in the future. @@ -123,7 +123,7 @@ func (suite *KeeperTestSuite) TestUnlockPeriodLockByID() { // Try to finish unlocking a lock, before starting unlock. lock, err = lockKeeper.GetLockByID(suite.Ctx, 1) suite.Require().NoError(err) - err = lockKeeper.Unlock(suite.Ctx, lock.ID) + err = lockKeeper.UnlockMaturedLock(suite.Ctx, lock.ID) suite.Require().Error(err) // begin unlock @@ -135,7 +135,7 @@ func (suite *KeeperTestSuite) TestUnlockPeriodLockByID() { // unlock 1s after begin unlock lock, err = lockKeeper.GetLockByID(suite.Ctx, 1) suite.Require().NoError(err) - err = lockKeeper.Unlock(suite.Ctx.WithBlockTime(now.Add(time.Second*2)), lock.ID) + err = lockKeeper.UnlockMaturedLock(suite.Ctx.WithBlockTime(now.Add(time.Second*2)), lock.ID) suite.Require().NoError(err) // check locks @@ -165,7 +165,7 @@ func (suite *KeeperTestSuite) TestUnlock() { suite.Require().NoError(err) // unlock with lock object - err = suite.App.LockupKeeper.Unlock(suite.Ctx.WithBlockTime(now.Add(time.Second)), lockPtr.ID) + err = suite.App.LockupKeeper.UnlockMaturedLock(suite.Ctx.WithBlockTime(now.Add(time.Second)), lockPtr.ID) suite.Require().NoError(err) } @@ -222,7 +222,7 @@ func (suite *KeeperTestSuite) TestPartialUnlock() { // Finish unlocking partial unlock partialUnlock := suite.App.LockupKeeper.GetAccountPeriodLocks(suite.Ctx, addr1)[1] - err = suite.App.LockupKeeper.Unlock(suite.Ctx.WithBlockTime(now.Add(time.Second)), partialUnlock.ID) + err = suite.App.LockupKeeper.UnlockMaturedLock(suite.Ctx.WithBlockTime(now.Add(time.Second)), partialUnlock.ID) suite.Require().NoError(err) // check unlocking coins diff --git a/x/lockup/keeper/synthetic_lock.go b/x/lockup/keeper/synthetic_lock.go index 401b47be3a8..79d6d5a6b0d 100644 --- a/x/lockup/keeper/synthetic_lock.go +++ b/x/lockup/keeper/synthetic_lock.go @@ -11,36 +11,19 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) setSyntheticLockupObject(ctx sdk.Context, synthLock *types.SyntheticLock) error { - store := ctx.KVStore(k.storeKey) - bz, err := proto.Marshal(synthLock) - if err != nil { - return err - } - store.Set(syntheticLockStoreKey(synthLock.UnderlyingLockId, synthLock.SynthDenom), bz) - if !synthLock.EndTime.Equal(time.Time{}) { - store.Set(syntheticLockTimeStoreKey(synthLock.UnderlyingLockId, synthLock.SynthDenom, synthLock.EndTime), bz) - } - return nil -} - -func (k Keeper) deleteSyntheticLockupObject(ctx sdk.Context, lockID uint64, synthdenom string) { - store := ctx.KVStore(k.storeKey) - synthLock, _ := k.GetSyntheticLockup(ctx, lockID, synthdenom) - if synthLock != nil && !synthLock.EndTime.Equal(time.Time{}) { - store.Delete(syntheticLockTimeStoreKey(lockID, synthdenom, synthLock.EndTime)) - } - store.Delete(syntheticLockStoreKey(lockID, synthdenom)) -} - -func (k Keeper) GetUnderlyingLock(ctx sdk.Context, synthlock types.SyntheticLock) types.PeriodLock { - lock, err := k.GetLockByID(ctx, synthlock.UnderlyingLockId) - if err != nil { - panic(err) // Synthetic lock MUST have underlying lock - } - return *lock -} - +// A synthetic lock object is a lock obejct used for the superfluid module. +// Each synthetic lock object is stored in state using lock id and synthetic denom +// as it's key, where a synthetic denom would be consisted of the original denom of the lock, +// validator address, and the staking positiion of the lock. +// Unlike the original lock objects, synthetic locks are mainly used to indicate the staking +// position of the lock. +// Synthetic use different accumulation store from the original lock objects. +// Note that locks with synthetic objects cannot be directly deleted or cannot directly start +// unlocking. locks with synthetic lock objects are to be unlocked via superfluid module. +// The Endtime and the Duration fields of the synthetic locks do not need to have the same values +// as the underlying lock objects. + +// GetSyntheticLockup gets the synthetic lock object using lock ID and synthetic denom as key. func (k Keeper) GetSyntheticLockup(ctx sdk.Context, lockID uint64, synthdenom string) (*types.SyntheticLock, error) { synthLock := types.SyntheticLock{} store := ctx.KVStore(k.storeKey) @@ -53,6 +36,7 @@ func (k Keeper) GetSyntheticLockup(ctx sdk.Context, lockID uint64, synthdenom st return &synthLock, err } +// GetAllSyntheticLockupsByLockup gets all the synthetic lockup object of the original lockup. func (k Keeper) GetAllSyntheticLockupsByLockup(ctx sdk.Context, lockID uint64) []types.SyntheticLock { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, combineKeys(types.KeyPrefixSyntheticLockup, sdk.Uint64ToBigEndian(lockID))) @@ -70,6 +54,7 @@ func (k Keeper) GetAllSyntheticLockupsByLockup(ctx sdk.Context, lockID uint64) [ return synthLocks } +// GetAllSyntheticLockupsByAddr gets all the synthetic lockups from all the locks owned by the given address. func (k Keeper) GetAllSyntheticLockupsByAddr(ctx sdk.Context, owner sdk.AccAddress) []types.SyntheticLock { synthLocks := []types.SyntheticLock{} locks := k.GetAccountPeriodLocks(ctx, owner) @@ -79,6 +64,7 @@ func (k Keeper) GetAllSyntheticLockupsByAddr(ctx sdk.Context, owner sdk.AccAddre return synthLocks } +// HasAnySyntheticLockups returns true if the lock has a synthetic lock. func (k Keeper) HasAnySyntheticLockups(ctx sdk.Context, lockID uint64) bool { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, combineKeys(types.KeyPrefixSyntheticLockup, sdk.Uint64ToBigEndian(lockID))) @@ -86,6 +72,7 @@ func (k Keeper) HasAnySyntheticLockups(ctx sdk.Context, lockID uint64) bool { return iterator.Valid() } +// GetAllSyntheticLockups gets all synthetic locks within the store. func (k Keeper) GetAllSyntheticLockups(ctx sdk.Context) []types.SyntheticLock { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.KeyPrefixSyntheticLockup) @@ -107,7 +94,7 @@ func (k Keeper) GetAllSyntheticLockups(ctx sdk.Context) []types.SyntheticLock { func (k Keeper) CreateSyntheticLockup(ctx sdk.Context, lockID uint64, synthDenom string, unlockDuration time.Duration, isUnlocking bool) error { // Note: synthetic lockup is doing everything same as lockup except coin movement // There is no relationship between unbonding and bonding synthetic lockup, it's managed separately - // Accumulation store works without caring about unlocking synthetic or not + // A separate accumulation store is incremented with the synth denom. _, err := k.GetSyntheticLockup(ctx, lockID, synthDenom) if err == nil { @@ -154,7 +141,27 @@ func (k Keeper) CreateSyntheticLockup(ctx sdk.Context, lockID uint64, synthDenom return nil } +// DeleteAllSyntheticLocks iterates over given array of synthetic locks and deletes all individual synthetic locks. +func (k Keeper) DeleteAllSyntheticLocks(ctx sdk.Context, lock types.PeriodLock, synthLocks []types.SyntheticLock) error { + if len(synthLocks) == 0 { + return nil + } + + for _, synthLock := range synthLocks { + err := k.DeleteSyntheticLockup(ctx, lock.ID, synthLock.SynthDenom) + if err != nil { + return err + } + } + return nil +} + // DeleteSyntheticLockup delete synthetic lockup with lock id and synthdenom. +// Synthetic lock has three relevant state entries. +// - synthetic lock object itself +// - synthetic lock refs +// - accumulation store for the synthetic lock. +// all of which are deleted within this method. func (k Keeper) DeleteSyntheticLockup(ctx sdk.Context, lockID uint64, synthdenom string) error { synthLock, err := k.GetSyntheticLockup(ctx, lockID, synthdenom) if err != nil { @@ -186,6 +193,7 @@ func (k Keeper) DeleteSyntheticLockup(ctx sdk.Context, lockID uint64, synthdenom return nil } +// DeleteAllMaturedSyntheticLocks deletes all matured synthetic locks. func (k Keeper) DeleteAllMaturedSyntheticLocks(ctx sdk.Context) { iterator := k.iteratorBeforeTime(ctx, combineKeys(types.KeyPrefixSyntheticLockTimestamp), ctx.BlockTime()) defer iterator.Close() @@ -202,3 +210,33 @@ func (k Keeper) DeleteAllMaturedSyntheticLocks(ctx sdk.Context) { } } } + +func (k Keeper) GetUnderlyingLock(ctx sdk.Context, synthlock types.SyntheticLock) types.PeriodLock { + lock, err := k.GetLockByID(ctx, synthlock.UnderlyingLockId) + if err != nil { + panic(err) // Synthetic lock MUST have underlying lock + } + return *lock +} + +func (k Keeper) setSyntheticLockupObject(ctx sdk.Context, synthLock *types.SyntheticLock) error { + store := ctx.KVStore(k.storeKey) + bz, err := proto.Marshal(synthLock) + if err != nil { + return err + } + store.Set(syntheticLockStoreKey(synthLock.UnderlyingLockId, synthLock.SynthDenom), bz) + if !synthLock.EndTime.Equal(time.Time{}) { + store.Set(syntheticLockTimeStoreKey(synthLock.UnderlyingLockId, synthLock.SynthDenom, synthLock.EndTime), bz) + } + return nil +} + +func (k Keeper) deleteSyntheticLockupObject(ctx sdk.Context, lockID uint64, synthdenom string) { + store := ctx.KVStore(k.storeKey) + synthLock, _ := k.GetSyntheticLockup(ctx, lockID, synthdenom) + if synthLock != nil && !synthLock.EndTime.Equal(time.Time{}) { + store.Delete(syntheticLockTimeStoreKey(lockID, synthdenom, synthLock.EndTime)) + } + store.Delete(syntheticLockStoreKey(lockID, synthdenom)) +} From 3afef8dc71d5ef91b78718941d804c16050a0e22 Mon Sep 17 00:00:00 2001 From: mattverse Date: Tue, 12 Jul 2022 18:08:50 +0900 Subject: [PATCH 3/3] Merge --- x/lockup/keeper/lock.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x/lockup/keeper/lock.go b/x/lockup/keeper/lock.go index 513f0ed9319..2ac861bda89 100644 --- a/x/lockup/keeper/lock.go +++ b/x/lockup/keeper/lock.go @@ -142,10 +142,7 @@ func (k Keeper) lock(ctx sdk.Context, lock types.PeriodLock, tokensToLock sdk.Co k.accumulationStore(ctx, coin.Denom).Increase(accumulationKey(lock.Duration), coin.Amount) } - if k.hooks != nil { - k.hooks.OnStartUnlock(ctx, lock.OwnerAddress(), lock.ID, lock.Coins, lock.Duration, lock.EndTime) - } - + k.hooks.OnTokenLocked(ctx, owner, lock.ID, lock.Coins, lock.Duration, lock.EndTime) return nil }