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

lockup: Allow rebonding of unbonding tokens #4713

Closed
wants to merge 66 commits into from
Closed
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
79bbd63
merge main
pysel Jul 9, 2023
8a84879
RebondTokens: unit test
pysel Mar 23, 2023
b438068
add cli
pysel Mar 24, 2023
6716f76
implement sdk.Msg
pysel Mar 25, 2023
780b8ae
better Long
pysel Mar 25, 2023
a72d187
code refactor
pysel Mar 25, 2023
bf195bc
test: RebondTokens
pysel Mar 25, 2023
016bb8f
finish rebond
pysel Mar 25, 2023
52055a6
cli
pysel Mar 25, 2023
3bfef36
make amount optional
pysel Mar 25, 2023
a0d48b0
fix tests
pysel Mar 25, 2023
c3d07e0
Update x/lockup/client/cli/tx.go
pysel Mar 25, 2023
f986221
Update x/lockup/keeper/lock.go
pysel Mar 25, 2023
d0db1c5
add implicit removal of all tokens test
pysel Mar 25, 2023
afc0f71
coins validation
pysel Mar 26, 2023
87e34c1
msgs: test added
pysel Mar 26, 2023
202a0ac
add no-op hooks
pysel Mar 26, 2023
31bd48f
rm hooks
pysel Mar 26, 2023
f1d17d7
rm wrong hook
pysel Mar 26, 2023
a8ebba5
Update x/lockup/client/cli/tx.go
pysel Apr 11, 2023
f5fdd19
Update x/lockup/keeper/lock.go
pysel Apr 11, 2023
2e472ae
Update x/lockup/keeper/lock.go
pysel Apr 14, 2023
50ac563
lock refs to test
pysel Apr 15, 2023
81b01f3
better test
pysel Apr 14, 2023
e5c3283
test refs
pysel Apr 15, 2023
b065be9
nit
pysel Apr 15, 2023
e0e7d35
improve naming in test
pysel May 10, 2023
6a3df68
address review: register errors
pysel May 11, 2023
0aae870
specify reward address when rebonding tokens
pysel Aug 24, 2023
c9d5a78
change code of ErrLockNotUnlocking (conflict with another error)
pysel Aug 24, 2023
8e7bbce
Auto: update go.mod after push to ruslan/rebonding-when-unbonding tha…
invalid-email-address Aug 29, 2023
d272db2
fix import paths
pysel Aug 29, 2023
adb12ce
update changelog
devbot-wizard Sep 26, 2023
a2f5dd3
Auto: update go.mod after push to ruslan/rebonding-when-unbonding tha…
invalid-email-address Sep 26, 2023
fdd8013
unblock ci
pysel Sep 26, 2023
0759709
deps
pysel Sep 26, 2023
89e57c6
Revert "unblock ci"
pysel Sep 26, 2023
470075b
tidy and add import
pysel Sep 26, 2023
541c433
Generated protofile changes
invalid-email-address Sep 26, 2023
d2b955e
remove hardcoded parameters in cli
pysel Sep 26, 2023
f051a3e
Update x/lockup/keeper/lock.go
pysel Sep 27, 2023
bffa4d1
update changelog
devbot-wizard Sep 26, 2023
e82137c
Auto: update go.mod after push to ruslan/rebonding-when-unbonding tha…
invalid-email-address Sep 26, 2023
dce2cff
add synth lock check
pysel Sep 27, 2023
71f740a
improve test: add synthetic lock testcase and remove code duplication
pysel Sep 27, 2023
eef1841
get rid of success field in rebonding message response
pysel Oct 4, 2023
0816b50
remove logic for partial rebonding
pysel Oct 4, 2023
ef0be05
synthetic lockup error
pysel Oct 4, 2023
7599444
remove partial rebonding
pysel Oct 4, 2023
be4b5aa
deps
pysel Sep 26, 2023
8e3ab60
fix changelog
pysel Oct 5, 2023
ebf4701
remove unneeded doc
pysel Oct 5, 2023
5c8c447
Merge branch 'main' into ruslan/rebonding-when-unbonding
czarcas7ic Oct 9, 2023
4b1887f
fix go mod
czarcas7ic Oct 9, 2023
141ab8b
Update x/lockup/keeper/lock.go
czarcas7ic Oct 9, 2023
bd7ce15
Update x/lockup/types/msgs_test.go
czarcas7ic Oct 9, 2023
1a93e64
Merge branch 'main' into ruslan/rebonding-when-unbonding
pysel Oct 10, 2023
4f185de
tidy after merge
pysel Oct 10, 2023
8a71c81
Fix CLI
mattverse Oct 11, 2023
458167f
Merge branch 'main' into ruslan/rebonding-when-unbonding
pysel Oct 13, 2023
fa5559b
Generated protofile changes
invalid-email-address Oct 13, 2023
6b193ba
post merge clearup
pysel Oct 13, 2023
85efccc
improve test and add review reqs
pysel Oct 13, 2023
bcafe70
partial unlocking + noise locks
pysel Oct 13, 2023
2736f8e
remove unneeded test case
pysel Oct 13, 2023
fb705a3
add docs
pysel Oct 13, 2023
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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Features

* [#6468](https://github.com/osmosis-labs/osmosis/pull/6468) feat: remove osmo multihop discount
* [#6420](https://github.com/osmosis-labs/osmosis/pull/6420) feat[CL]: Creates a governance set whitelist of addresses that can bypass the normal pool creation restrictions on concentrated liquidity pools
* [#6632](https://github.com/osmosis-labs/osmosis/pull/6632) Taker fee bypass whitelist
* [#6420](https://github.com/osmosis-labs/osmosis/pull/6420) feat[CL]: creates a governance set whitelist of addresses that can bypass the normal pool creation restrictions on concentrated liquidity pools
* [#4713](https://github.com/osmosis-labs/osmosis/pull/4713) feat: allow rebonding of unbonding tokens
* [#6632](https://github.com/osmosis-labs/osmosis/pull/6632) feat: taker fee bypass whitelist

### State Breaking

Expand Down
1 change: 0 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1243,7 +1243,6 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
Expand Down
1 change: 1 addition & 0 deletions osmoutils/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ require (
github.com/rs/cors v1.8.2 // indirect
github.com/rs/zerolog v1.27.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
Expand Down
9 changes: 8 additions & 1 deletion proto/osmosis/lockup/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ service Msg {
// SetRewardReceiverAddress edits the reward receiver for the given lock ID
rpc SetRewardReceiverAddress(MsgSetRewardReceiverAddress)
returns (MsgSetRewardReceiverAddressResponse);
rpc RebondTokens(MsgRebondTokens) returns (MsgRebondTokensResponse);
}

message MsgLockTokens {
Expand Down Expand Up @@ -112,4 +113,10 @@ message MsgSetRewardReceiverAddress {
string reward_receiver = 3
[ (gogoproto.moretags) = "yaml:\"reward_receiver\"" ];
}
message MsgSetRewardReceiverAddressResponse { bool success = 1; }
message MsgSetRewardReceiverAddressResponse { bool success = 1; }
message MsgRebondTokens {
string owner = 1 [ (gogoproto.moretags) = "yaml:\"owner\"" ];
uint64 ID = 2;
}

message MsgRebondTokensResponse {}
1 change: 1 addition & 0 deletions tests/cl-genesis-positions/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ require (
github.com/rs/cors v1.8.2 // indirect
github.com/rs/zerolog v1.27.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/cobra v1.7.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions tests/cl-go-client/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ require (
github.com/rs/cors v1.8.2 // indirect
github.com/rs/zerolog v1.27.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/cobra v1.7.0 // indirect
Expand Down
5 changes: 0 additions & 5 deletions x/epochs/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE=
cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04=
cosmossdk.io/math v1.1.3-rc.0 h1:lut7IbQGuqog4vEIHSB7ivtwwIr6rHrt5yoVowmtoDs=
cosmossdk.io/math v1.1.3-rc.0/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
Expand Down Expand Up @@ -165,7 +164,6 @@ github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAc
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
Expand Down Expand Up @@ -830,7 +828,6 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
Expand Down Expand Up @@ -1184,7 +1181,6 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down Expand Up @@ -1271,7 +1267,6 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
6 changes: 6 additions & 0 deletions x/lockup/client/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ func FlagSetLockTokens() *flag.FlagSet {
return fs
}

func FlagSetRebondTokens() *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
mattverse marked this conversation as resolved.
Show resolved Hide resolved
fs.String(FlagAmount, "", "The amount to be rebonded. e.g. 1osmo")
return fs
}

func FlagSetUnlockTokens() *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.String(FlagAmount, "", "The amount to be unlocked. e.g. 1osmo")
Expand Down
18 changes: 18 additions & 0 deletions x/lockup/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func GetTxCmd() *cobra.Command {
osmocli.AddTxCmd(cmd, NewBeginUnlockByIDCmd)
osmocli.AddTxCmd(cmd, NewForceUnlockByIdCmd)
osmocli.AddTxCmd(cmd, NewSetRewardReceiverAddress)
osmocli.AddTxCmd(cmd, NewRebondTokensCmd)

return cmd
}
Expand Down Expand Up @@ -72,3 +73,20 @@ func NewSetRewardReceiverAddress() (*osmocli.TxCliDesc, *types.MsgSetRewardRecei
Long: "sets reward receiver address for the designated lock id",
}, &types.MsgSetRewardReceiverAddress{}
}

// NewRebondTokensCmd allows a user to rebond their tokens if they have unlocked them.
func NewRebondTokensCmd() (*osmocli.TxCliDesc, *types.MsgRebondTokens) {
return &osmocli.TxCliDesc{
Use: "rebond-tokens",
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
Short: "rebond unlocking tokens",
Long: `Rebond unlocking tokens. If the amount flag is not specified, will rebond all tokens

Example:
osmosisd tx lockup rebond-tokens 1
`,
CustomFlagOverrides: map[string]string{
"coins": FlagAmount,
pysel marked this conversation as resolved.
Show resolved Hide resolved
},
Flags: osmocli.FlagDesc{OptionalFlags: []*pflag.FlagSet{FlagSetRebondTokens()}},
}, &types.MsgRebondTokens{}
}
16 changes: 16 additions & 0 deletions x/lockup/keeper/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,19 @@ func (k Keeper) Lock(ctx sdk.Context, lock types.PeriodLock, tokensToLock sdk.Co
func (k Keeper) UnlockMaturedLockInternalLogic(ctx sdk.Context, lock types.PeriodLock) error {
return k.unlockMaturedLockInternalLogic(ctx, lock)
}

func DurationLockRefKeys(lock types.PeriodLock) ([][]byte, error) {
return durationLockRefKeys(lock)
}

func LockRefKeys(lock types.PeriodLock) ([][]byte, error) {
return lockRefKeys(lock)
}

func CombineKeys(keys ...[]byte) []byte {
return combineKeys(keys...)
}

func UnlockingPrefix(unlocking bool) []byte {
return unlockingPrefix(unlocking)
}
9 changes: 5 additions & 4 deletions x/lockup/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (
"github.com/osmosis-labs/osmosis/v19/x/lockup/types"
)

func (s *KeeperTestSuite) LockTokens(addr sdk.AccAddress, coins sdk.Coins, duration time.Duration) {
s.FundAcc(addr, coins)
_, err := s.querier.CreateLock(s.Ctx, addr, coins, duration)
s.Require().NoError(err)
func (suite *KeeperTestSuite) LockTokens(addr sdk.AccAddress, coins sdk.Coins, duration time.Duration) uint64 {
suite.FundAcc(addr, coins)
lock, err := suite.querier.CreateLock(suite.Ctx, addr, coins, duration)
suite.Require().NoError(err)
return lock.ID
}

func (s *KeeperTestSuite) BeginUnlocking(addr sdk.AccAddress) {
Expand Down
44 changes: 44 additions & 0 deletions x/lockup/keeper/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -904,3 +904,47 @@ func (k Keeper) getCoinsFromLocks(locks []types.PeriodLock) sdk.Coins {
}
return coins
}

// RebondTokens rebonds some amount or all coins of a lock with id lockID only if it is already unbonding
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
func (k Keeper) RebondTokens(ctx sdk.Context, lockID uint64, owner sdk.AccAddress) error {
lock, err := k.GetLockByID(ctx, lockID)
if err != nil {
return err
}

pysel marked this conversation as resolved.
Show resolved Hide resolved
// check synthetic lockup exists
if k.HasAnySyntheticLockups(ctx, lock.ID) {
return errorsmod.Wrap(types.ErrLockHasSyntheticLockup, fmt.Sprintf("cannot edit lockup with synthetic lock %d", lock.ID))
}

// check that lock's owner is the same as a message sender
if lock.Owner != owner.String() {
return errorsmod.Wrap(types.ErrNotLockOwner, fmt.Sprintf("lock %d is not owned by %s", lockID, owner))
}

if !lock.IsUnlocking() {
return errorsmod.Wrap(types.ErrLockNotUnlocking, fmt.Sprintf("lock %d is not unlocking, rebonding only possible in unlocking stage", lockID))
}

// Restart lock timer and set back to the store
// Rebonded lock is the same lock as the original lock, but with an empty EndTime.
rebondedLock := types.NewPeriodLock(lock.ID, lock.OwnerAddress(), lock.RewardReceiverAddress, lock.Duration, time.Time{}, lock.Coins)
err = k.setLock(ctx, rebondedLock)
if err != nil {
return err
}

// Remove original lock's refs from state
err = k.deleteLockRefs(ctx, types.KeyPrefixUnlocking, *lock)
if err != nil {
return err
}

// Add refs for a newly created lock
err = k.addLockRefs(ctx, rebondedLock)
if err != nil {
return err
}

return nil
}
135 changes: 135 additions & 0 deletions x/lockup/keeper/lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,119 @@ import (
"github.com/osmosis-labs/osmosis/osmomath"
cl "github.com/osmosis-labs/osmosis/v19/x/concentrated-liquidity"
cltypes "github.com/osmosis-labs/osmosis/v19/x/concentrated-liquidity/types"
"github.com/osmosis-labs/osmosis/v19/x/lockup/keeper"
"github.com/osmosis-labs/osmosis/v19/x/lockup/types"
lockuptypes "github.com/osmosis-labs/osmosis/v19/x/lockup/types"

sdk "github.com/cosmos/cosmos-sdk/types"
)

func (suite *KeeperTestSuite) TestRebondTokens() {
pysel marked this conversation as resolved.
Show resolved Hide resolved
// coins that will be locked
lockedCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 10))

addr1 := suite.TestAccs[0]
defaultLockID := uint64(1)
nonExistingLockID := uint64(2)
notOwnerAddress := suite.TestAccs[1]

testCases := []struct {
pysel marked this conversation as resolved.
Show resolved Hide resolved
name string
unlock bool
rebondLockID uint64
createSyntheticLock bool
owner sdk.AccAddress

expectedError error
}{
{
name: "Valid test case",
unlock: true,
owner: addr1,
rebondLockID: defaultLockID,
},
{
name: "Invalid: Trying to rebond a non existent lock id",
unlock: true,
owner: addr1,
rebondLockID: nonExistingLockID,
expectedError: types.ErrLockupNotFound,
},
{
name: "Invalid: Trying to rebond a non-unbonding lock",
owner: addr1,
rebondLockID: defaultLockID,
expectedError: types.ErrLockNotUnlocking,
},
{
name: "Invalid: Trying to rebond a synthetic's underlying lock",
createSyntheticLock: true,
owner: addr1,
rebondLockID: defaultLockID,
expectedError: types.ErrLockHasSyntheticLockup,
},
{
name: "Invalid: Not the owner of a lock",
unlock: true,
owner: notOwnerAddress,
rebondLockID: defaultLockID,
expectedError: types.ErrNotLockOwner,
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest()

// lock coins
lockID := suite.LockTokens(addr1, lockedCoins, time.Second)

if tc.unlock {
// unlock coins
_, err := suite.App.LockupKeeper.BeginUnlock(suite.Ctx, lockID, lockedCoins)
suite.Require().NoError(err)
}

initialLock, err := suite.App.LockupKeeper.GetLockByID(suite.Ctx, lockID)
suite.Require().NoError(err)

// a sanity check to make sure the lock is in the correct state
if tc.unlock {
suite.Require().True(initialLock.IsUnlocking())
Copy link
Contributor

Choose a reason for hiding this comment

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

Would you be able to add an additional set of test cases (or extend these ones by adding a new test field for time elapsed) that covers cases where time has elapsed since the lock started unlocking? Specifically, it is important we test behavior around when:

  • the lock is partially finished unlocking (should function same as current tests)
  • the lock is exactly finished unlocking (should not allow rebonding, as it should no longer be unlocking)
  • the lock is past the unlocking period (should fail in all cases)

It would also be great to have cases covering behavior where BeginUnlock is run on part (not all) of lockedCoins, as this triggers a different logic branch that uses SplitLock that is currently untested.

Finally, we should also have test cases that include the creation of other locks and assert that they are unchanged.

Copy link
Member Author

Choose a reason for hiding this comment

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

hey @AlpinYukseloglu! Those are great points, thank you for your review!

Note: I changed the logic of tests to make it as readable as possible. Now, in a test case struct I include so called setupFunctions that are being ran prior to testing. These include: creation of initial lock, unlocking the lock, creating synthetic lockup, etc.

I addressed your comments in latest commits, here is a breakdown of everything for an easier review process:

  1. Partially finished unlocking lock
  2. Exactly finished unlocking lock
  3. Past unlocking period
  4. BeginUnlock on some fraction of locked coins
  5. Assertion that other locks are unchanged

Though, I have one concern about exactly unlocked lock (2). (2) works because I run EndBlocker in the test prior to rebonding a lock. Because of that, it automatically removes the lock if the current block time is exactly the endTime of a lock (matured).

However, in real scenario, when transactions of a block with block time equal to endTime of an unlocking lock are being executed, if someone tries to rebond this lock, they will be able to do so, since it was not yet removed from the store. I am a little confused as to why in case (2), we should fail? Since unlocking of maturing locks happens in EndBlocker, I do not see the reason for forbidding case (2) from passing

Copy link
Member Author

Choose a reason for hiding this comment

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

tagging for re-review of the new test

cc: @mattverse @p0mvn

Copy link
Member Author

Choose a reason for hiding this comment

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

Adding on case (2): basically, the test works right now by simulating an unreal scenario - by calling endBlocker and then running rebonding logic both with the same blocktime is essentially equivalent to a scenario, when two consecutive blocks have the same block time

Copy link
Member

Choose a reason for hiding this comment

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

I think case 2 correct way would be being able to rebond if endblocker has not been called yet

}

// underlying synthetic locks' period locks cannot be rebonded
if tc.createSyntheticLock {
err := suite.App.LockupKeeper.CreateSyntheticLockup(suite.Ctx, lockID, "synthetic", initialLock.Duration, false)
suite.Require().NoError(err)
}

// rebond coins
err = suite.App.LockupKeeper.RebondTokens(suite.Ctx, tc.rebondLockID, tc.owner)

if tc.expectedError != nil {
suite.Require().ErrorIs(err, tc.expectedError)
return
}
suite.Require().NoError(err)

// get rebonded lock
lock, err := suite.App.LockupKeeper.GetLockByID(suite.Ctx, lockID)
suite.Require().NoError(err)

// Assertions
suite.Require().Equal(lock.Owner, initialLock.Owner)
suite.Require().Equal(lock.Duration, initialLock.Duration)
suite.Require().Equal(lock.ID, initialLock.ID)
suite.Require().Equal(lock.Coins, initialLock.Coins)
suite.Require().Equal(lock.RewardReceiverAddress, initialLock.RewardReceiverAddress)
suite.Require().False(lock.IsUnlocking())

// Check lock refs
suite.assertLockRefs(*lock)
})
}
}
func (s *KeeperTestSuite) TestBeginUnlocking() { // test for all unlockable coins
s.SetupTest()

Expand Down Expand Up @@ -1579,3 +1686,31 @@ func (s *KeeperTestSuite) TestPartialForceUnlock() {
}
}
}

// assertLockRefs checks that a lock has required refs in store based on the current state of the lock
// Ex: if lock is unlocking, it should have unlocking refs
func (suite *KeeperTestSuite) assertLockRefs(lock types.PeriodLock) {
refKeys, err := keeper.DurationLockRefKeys(lock)
suite.Require().NoError(err)

if lock.IsUnlocking() {
refKeys, err = keeper.LockRefKeys(lock)
suite.Require().NoError(err)
}

keyPrefix := keeper.UnlockingPrefix(lock.IsUnlocking())
for _, refKey := range refKeys {
refKey = keeper.CombineKeys(keyPrefix, refKey)
ids := suite.App.LockupKeeper.GetLockRefs(suite.Ctx, refKey)

found := false
for _, id := range ids {
if lock.ID == id {
found = true
pysel marked this conversation as resolved.
Show resolved Hide resolved
break
}
}

suite.Require().True(found)
}
}
Loading