Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix v10.x state incompatability #2268

Merged
merged 4 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## v10.2.0

In v10.1.0 a refactor did changed the ordering of certain function calls in a message execution.
The data read and written to in state were the same, but re-ordered.
This unknowingly caused gas_used incompatabilities, because you could out-of-gas error at any intermediate step, and end in a different gas_used quantity. cref: https://github.com/cosmos/cosmos-sdk/issues/12788

The v10.2 line reverts the gas limit state incompatabilities introduced due to function ordering.
Namely this reverts

* [#1937](https://github.com/osmosis-labs/osmosis/pull/1937) Change `lockupKeeper.ExtendLock` to take in lockID instead of the direct lock struct.
* [#2030](https://github.com/osmosis-labs/osmosis/pull/2030) Rename lockup keeper `ResetAllLocks` to `InitializeAllLocks` and `ResetAllSyntheticLocks` to `InitializeAllSyntheticLocks`.

## v10.1.1

#### Improvements
Expand Down
8 changes: 5 additions & 3 deletions x/lockup/keeper/admin_keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"

"github.com/osmosis-labs/osmosis/v10/x/lockup/keeper"
"github.com/osmosis-labs/osmosis/v10/x/lockup/types"

sdk "github.com/cosmos/cosmos-sdk/types"
)
Expand All @@ -13,10 +14,11 @@ 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)
lock, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, addr1, coins, time.Second)
err := suite.App.LockupKeeper.Lock(suite.Ctx, lock)
suite.Require().NoError(err)

// lock with balance with same id
Expand All @@ -36,12 +38,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)

lock, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, addr1, coins, time.Second)

err := suite.App.LockupKeeper.Lock(suite.Ctx, lock)
suite.Require().NoError(err)

// break lock
Expand Down
4 changes: 0 additions & 4 deletions x/lockup/keeper/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,3 @@ 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)
}
110 changes: 61 additions & 49 deletions x/lockup/keeper/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,79 +66,100 @@ func (k Keeper) AddToExistingLock(ctx sdk.Context, owner sdk.AccAddress, coin sd
// 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) {
func (k Keeper) AddTokensToLockByID(ctx sdk.Context, lockID uint64, owner sdk.AccAddress, coin sdk.Coin) (*types.PeriodLock, error) {
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if err := k.bk.SendCoinsFromAccountToModule(ctx, lock.OwnerAddress(), types.ModuleName, sdk.NewCoins(coin)); err != nil {
if err := k.bk.SendCoinsFromAccountToModule(ctx, lock.OwnerAddress(), types.ModuleName, sdk.NewCoins(coin)); err != nil {

return nil, err
}

for _, synthlock := range k.GetAllSyntheticLockupsByLockup(ctx, lock.ID) {
k.accumulationStore(ctx, synthlock.SynthDenom).Increase(accumulationKey(synthlock.Duration), tokensToAdd.Amount)
err = k.addTokenToLock(ctx, lock, coin)
if err != nil {
return nil, err
}

if k.hooks == nil {
return lock, nil
}

k.hooks.AfterAddTokensToLock(ctx, lock.OwnerAddress(), lock.GetID(), sdk.NewCoins(tokensToAdd))

k.hooks.OnTokenLocked(ctx, lock.OwnerAddress(), lock.ID, sdk.Coins{coin}, lock.Duration, lock.EndTime)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is OnTokenLocked hook called when AfterAddTokensToLock is already called above? Are both the hook calls necessary?

return lock, nil
}

// addTokenToLock adds token to lock and modifies the state of the lock and the accumulation store.
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)

// CONTRACT: lock will have synthetic lock only if it has a single coin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// CONTRACT: lock will have synthetic lock only if it has a single coin
// CONTRACT: Lock will have synthetic lock only if it has a single coin

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a revert, can make these changes happen in another PR if we really care

// accumulation store for its synthetic denom is increased if exists.
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
}

// 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
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 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)
err := k.Lock(ctx, lock)
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
}

// 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 {
// Lock is a utility method to lock tokens into the module account. This method includes setting the
// lock within the state machine and increasing the value of accumulation store.
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, tokensToLock); err != nil {
if err := k.bk.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, lock.Coins); err != nil {
return err
}

// store lock object into the store
err = k.setLock(ctx, lock)
store := ctx.KVStore(k.storeKey)
bz, err := proto.Marshal(&lock)
if err != nil {
return err
}
store.Set(lockStoreKey(lock.ID), bz)

stackman27 marked this conversation as resolved.
Show resolved Hide resolved
// add lock refs into not unlocking queue
err = k.addLockRefs(ctx, lock)
if err != nil {
return err
}

// add to accumulation store
for _, coin := range tokensToLock {
for _, coin := range lock.Coins {
k.accumulationStore(ctx, coin.Denom).Increase(accumulationKey(lock.Duration), coin.Amount)
}

Expand Down Expand Up @@ -353,16 +374,7 @@ func (k Keeper) unlockMaturedLockInternalLogic(ctx sdk.Context, lock types.Perio
// 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, lockID uint64, owner sdk.AccAddress, newDuration time.Duration) error {
lock, err := k.GetLockByID(ctx, lockID)
if err != nil {
return err
}

if lock.GetOwner() != owner.String() {
return types.ErrNotLockOwner
}

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)
}
Expand All @@ -372,15 +384,10 @@ func (k Keeper) ExtendLockup(ctx sdk.Context, lockID uint64, owner sdk.AccAddres
return fmt.Errorf("cannot edit lockup with synthetic lock %d", lock.ID)
}

// completely delete existing lock refs
err = k.deleteLockRefs(ctx, unlockingPrefix(lock.IsUnlocking()), *lock)
if err != nil {
return err
}
oldLock := lock

oldDuration := lock.GetDuration()
if newDuration != 0 {
if newDuration <= oldDuration {
if newDuration <= lock.Duration {
return fmt.Errorf("new duration should be greater than the original")
}

Expand All @@ -393,20 +400,25 @@ func (k Keeper) ExtendLockup(ctx sdk.Context, lockID uint64, owner sdk.AccAddres
lock.Duration = newDuration
}

// add lock refs with the new duration
err = k.addLockRefs(ctx, *lock)
// update lockup
err := k.deleteLockRefs(ctx, unlockingPrefix(oldLock.IsUnlocking()), oldLock)
if err != nil {
return err
}

err = k.setLock(ctx, *lock)
err = k.addLockRefs(ctx, lock)
if err != nil {
return err
}

err = k.setLock(ctx, lock)
if err != nil {
return err
}

k.hooks.OnLockupExtend(ctx,
lock.GetID(),
oldDuration,
oldLock.GetDuration(),
lock.GetDuration(),
)

Expand Down
Loading