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(x/accounts/default/lockup) Lockup account track undelegation when unbonding entry is mature #22254

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4fd342f
add und entry proto
sontrinh16 Oct 14, 2024
83d300d
add track ubd entries
sontrinh16 Oct 14, 2024
41a1808
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Oct 14, 2024
a63f909
add track undelegations before check tokens sendable
sontrinh16 Oct 15, 2024
0544b0d
Merge branch 'son/lockup_unbond_sync' of https://github.com/cosmos/co…
sontrinh16 Oct 15, 2024
a37205e
minor
sontrinh16 Oct 15, 2024
e1e0ce2
fix tests
sontrinh16 Oct 15, 2024
b1135b6
fix tests
sontrinh16 Oct 15, 2024
716621a
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Oct 16, 2024
7641b26
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Oct 16, 2024
700ea01
manually track undelegation
sontrinh16 Oct 16, 2024
0c26ac3
Merge branch 'son/lockup_unbond_sync' of https://github.com/cosmos/co…
sontrinh16 Oct 16, 2024
2eaff98
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Oct 16, 2024
8ff7f82
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Oct 21, 2024
f157e17
Update x/accounts/proto/cosmos/accounts/defaults/lockup/lockup.proto
sontrinh16 Oct 23, 2024
de6f925
fix conflict
sontrinh16 Oct 28, 2024
075d9ee
Merge branch 'son/lockup_unbond_sync' of https://github.com/cosmos/co…
sontrinh16 Oct 28, 2024
9f25810
minor
sontrinh16 Oct 28, 2024
22d24c1
fix tests
sontrinh16 Oct 28, 2024
c990e46
minor
sontrinh16 Oct 28, 2024
8e9bde2
fix tests
sontrinh16 Oct 28, 2024
70e61c5
minor
sontrinh16 Oct 28, 2024
4158151
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Oct 29, 2024
e0b096e
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Oct 30, 2024
7e5d20e
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Oct 31, 2024
2a8d551
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Nov 5, 2024
4e83bc7
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Nov 20, 2024
646c178
Merge branch 'son/lockup_unbond_sync' of https://github.com/cosmos/co…
sontrinh16 Nov 26, 2024
f9dcc39
remove ubd id
sontrinh16 Nov 28, 2024
f9d1bfb
fixing
sontrinh16 Dec 2, 2024
8e3be3d
fix tests
sontrinh16 Dec 2, 2024
7fb8c29
add remove matured entry
sontrinh16 Dec 2, 2024
ed0cbdb
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Dec 2, 2024
1ed4b64
revert
sontrinh16 Dec 2, 2024
a7ee203
fix tests
sontrinh16 Dec 2, 2024
af32a94
more fixing
sontrinh16 Dec 2, 2024
54c8f5c
remove withdraw unlocked coins
sontrinh16 Dec 4, 2024
a4c8c3e
clean up
sontrinh16 Dec 4, 2024
292cf01
address comments
sontrinh16 Dec 9, 2024
ce829cf
minor
sontrinh16 Dec 9, 2024
be528f7
Merge branch 'main' into son/lockup_unbond_sync
sontrinh16 Dec 9, 2024
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
814 changes: 771 additions & 43 deletions api/cosmos/accounts/defaults/lockup/lockup.pulsar.go

Large diffs are not rendered by default.

1,058 changes: 1,005 additions & 53 deletions api/cosmos/accounts/defaults/lockup/query.pulsar.go

Large diffs are not rendered by default.

686 changes: 609 additions & 77 deletions api/cosmos/accounts/defaults/lockup/tx.pulsar.go

Large diffs are not rendered by default.

24 changes: 20 additions & 4 deletions tests/e2e/accounts/lockup/continous_lockup_test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (s *E2ETestSuite) TestContinuousLockingAccount() {
ctx := sdk.NewContext(app.CommitMultiStore(), false, app.Logger()).WithHeaderInfo(header.Info{
Time: currentTime,
})
s.setupStakingParams(ctx, app)
ownerAddrStr, err := app.AuthKeeper.AddressCodec().BytesToString(accOwner)
require.NoError(t, err)
s.fundAccount(app, ctx, accOwner, sdk.Coins{sdk.NewCoin("stake", math.NewInt(1000000))})
Expand Down Expand Up @@ -163,17 +164,32 @@ func (s *E2ETestSuite) TestContinuousLockingAccount() {
require.NoError(t, err)
require.Equal(t, len(ubd.Entries), 1)

// check if tracking is updated accordingly
lockupAccountInfoResponse := s.queryLockupAccInfo(ctx, app, accountAddr)
delLocking := lockupAccountInfoResponse.DelegatedLocking
require.True(t, delLocking.AmountOf("stake").Equal(math.ZeroInt()))
// check if an entry is added
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent potential index out-of-range panic when accessing entries[0].

Before accessing entries[0], add a check to ensure that the entries slice is not empty. This avoids a possible runtime panic if the slice is empty.

Apply this diff to add the necessary check:

+       require.NotEmpty(t, entries)
        require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
        require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// check if an entry is added
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
// check if an entry is added
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.NotEmpty(t, entries)
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)

})

// Update context time to end time
ctx = ctx.WithHeaderInfo(header.Info{
Time: currentTime.Add(time.Minute),
})

t.Run("ok - execute tracking unbonding entry", func(t *testing.T) {
msg := &types.MsgTrackUndelegation{
Sender: ownerAddrStr,
Id: 0,
}
err = s.executeTx(ctx, msg, app, accountAddr, accOwner)
require.NoError(t, err)

// check if tracking is updated accordingly
lockupAccountInfoResponse := s.queryLockupAccInfo(ctx, app, accountAddr)
delLocking := lockupAccountInfoResponse.DelegatedLocking
require.True(t, delLocking.AmountOf("stake").Equal(math.ZeroInt()))
})
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be helpful to cover the unhappy path when there is no unbonding or the unboding is not mature, yet.

Personal preference: this is a big test method already. Would it make sense to have a LockupE2ETestSuite checks the common operations and focus on the subtype specifics? I am thinking a bit about the way the storage tests are done for the different DBs

Copy link
Member Author

Choose a reason for hiding this comment

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

like spliting to multiple tests method for each actions right ?


// test if tracking delegate work perfectly
t.Run("ok - execute delegate message", func(t *testing.T) {
msg := &types.MsgDelegate{
Expand Down
22 changes: 22 additions & 0 deletions tests/e2e/accounts/lockup/delayed_lockup_test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (s *E2ETestSuite) TestDelayedLockingAccount() {
ctx := sdk.NewContext(app.CommitMultiStore(), false, app.Logger()).WithHeaderInfo(header.Info{
Time: currentTime,
})
s.setupStakingParams(ctx, app)
ownerAddrStr, err := app.AuthKeeper.AddressCodec().BytesToString(accOwner)
require.NoError(t, err)
s.fundAccount(app, ctx, accOwner, sdk.Coins{sdk.NewCoin("stake", math.NewInt(1000000))})
Expand Down Expand Up @@ -125,6 +126,27 @@ func (s *E2ETestSuite) TestDelayedLockingAccount() {
require.NoError(t, err)
require.Equal(t, len(ubd.Entries), 1)

// check if an entry is added
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent potential panic by verifying entries is not empty before access

Accessing entries[0] without confirming that entries has at least one element could cause an index out of range panic if the slice is empty. Please add a check to ensure entries is not empty before accessing its first element.

Apply this diff to fix the issue:

+		require.NotEmpty(t, entries)
		require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
		require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// check if an entry is added
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
})
// check if an entry is added
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.NotEmpty(t, entries)
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
})


// Update context time
// After unbond time elapsed
ctx = ctx.WithHeaderInfo(header.Info{
Time: currentTime.Add(time.Second * 11),
})

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use unbonding duration constant instead of hardcoding

Currently, the unbonding time is hardcoded as time.Second * 11. To improve maintainability and clarity, consider using the actual unbonding period set in setupStakingParams or defining a constant that represents the unbonding duration.

Apply this diff:

-	ctx = ctx.WithHeaderInfo(header.Info{
-		Time: currentTime.Add(time.Second * 11),
-	})
+	unbondingDuration := /* retrieve unbonding duration from staking params */
+	ctx = ctx.WithHeaderInfo(header.Info{
+		Time: currentTime.Add(unbondingDuration),
+	})

Ensure that unbondingDuration accurately reflects the unbonding period defined in your staking parameters.

Committable suggestion was skipped due to low confidence.

t.Run("ok - execute tracking unbonding entry", func(t *testing.T) {
msg := &types.MsgTrackUndelegation{
Sender: ownerAddrStr,
Id: 0,
}
err = s.executeTx(ctx, msg, app, accountAddr, accOwner)
require.NoError(t, err)

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance test coverage by adding cases for multiple and invalid unbonding entries

The current test covers tracking a single unbonding entry with Id: 0. To ensure robustness and comprehensive coverage, consider adding test cases that:

  • Track multiple unbonding entries to verify correct handling when multiple entries exist.
  • Attempt to track an unbonding entry with an invalid or non-existent Id and confirm that the appropriate error is returned.

// check if tracking is updated accordingly
lockupAccountInfoResponse := s.queryLockupAccInfo(ctx, app, accountAddr)
delLocking := lockupAccountInfoResponse.DelegatedLocking
Expand Down
24 changes: 20 additions & 4 deletions tests/e2e/accounts/lockup/periodic_lockup_test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (s *E2ETestSuite) TestPeriodicLockingAccount() {
ctx := sdk.NewContext(app.CommitMultiStore(), false, app.Logger()).WithHeaderInfo(header.Info{
Time: currentTime,
})
s.setupStakingParams(ctx, app)
ownerAddrStr, err := app.AuthKeeper.AddressCodec().BytesToString(accOwner)
require.NoError(t, err)
s.fundAccount(app, ctx, accOwner, sdk.Coins{sdk.NewCoin("stake", math.NewInt(1000000))})
Expand Down Expand Up @@ -185,10 +186,11 @@ func (s *E2ETestSuite) TestPeriodicLockingAccount() {
require.NoError(t, err)
require.Equal(t, len(ubd.Entries), 1)

// check if tracking is updated accordingly
lockupAccountInfoResponse := s.queryLockupAccInfo(ctx, app, accountAddr)
delLocking := lockupAccountInfoResponse.DelegatedLocking
require.True(t, delLocking.AmountOf("stake").Equal(math.ZeroInt()))
// check if an entry is added
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure unbonding entries are not empty before accessing

Accessing entries[0] without verifying that entries is non-empty may result in an index out of range panic if there are no unbonding entries. It's recommended to check that len(entries) > 0 before accessing entries[0].

Apply this diff to add a length check:

+    require.True(t, len(entries) > 0)
     require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
     require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.True(t, len(entries) > 0)
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)

})

// Update context time
Expand All @@ -197,6 +199,20 @@ func (s *E2ETestSuite) TestPeriodicLockingAccount() {
Time: currentTime.Add(time.Minute * 3),
})

t.Run("ok - execute tracking unbonding entry", func(t *testing.T) {
msg := &types.MsgTrackUndelegation{
Sender: ownerAddrStr,
Id: 0,
}
err = s.executeTx(ctx, msg, app, accountAddr, accOwner)
require.NoError(t, err)

// check if tracking is updated accordingly
lockupAccountInfoResponse := s.queryLockupAccInfo(ctx, app, accountAddr)
delLocking := lockupAccountInfoResponse.DelegatedLocking
require.True(t, delLocking.AmountOf("stake").Equal(math.ZeroInt()))
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid hardcoding unbonding entry ID; obtain it dynamically

Currently, the unbonding entry Id is hardcoded to 0 in MsgTrackUndelegation. If unbonding entries have varying IDs or the order changes, this hardcoding may cause the test to fail. Consider retrieving the unbonding entry ID dynamically from the unbonding entries response.

Apply this diff to obtain the entry ID dynamically:

     t.Run("ok - execute tracking unbonding entry", func(t *testing.T) {
+        // Retrieve the unbonding entries to get the correct ID
+        unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
+        entries := unbondingEntriesResponse.UnbondingEntries
+        require.True(t, len(entries) > 0)
+        entryID := entries[0].Id

         msg := &types.MsgTrackUndelegation{
             Sender: ownerAddrStr,
-            Id:     0,
+            Id:     entryID,
         }
         err = s.executeTx(ctx, msg, app, accountAddr, accOwner)
         require.NoError(t, err)

         // check if tracking is updated accordingly
         lockupAccountInfoResponse := s.queryLockupAccInfo(ctx, app, accountAddr)
         delLocking := lockupAccountInfoResponse.DelegatedLocking
         require.True(t, delLocking.AmountOf("stake").Equal(math.ZeroInt()))
     })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
t.Run("ok - execute tracking unbonding entry", func(t *testing.T) {
msg := &types.MsgTrackUndelegation{
Sender: ownerAddrStr,
Id: 0,
}
err = s.executeTx(ctx, msg, app, accountAddr, accOwner)
require.NoError(t, err)
// check if tracking is updated accordingly
lockupAccountInfoResponse := s.queryLockupAccInfo(ctx, app, accountAddr)
delLocking := lockupAccountInfoResponse.DelegatedLocking
require.True(t, delLocking.AmountOf("stake").Equal(math.ZeroInt()))
})
t.Run("ok - execute tracking unbonding entry", func(t *testing.T) {
// Retrieve the unbonding entries to get the correct ID
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.True(t, len(entries) > 0)
entryID := entries[0].Id
msg := &types.MsgTrackUndelegation{
Sender: ownerAddrStr,
Id: entryID,
}
err = s.executeTx(ctx, msg, app, accountAddr, accOwner)
require.NoError(t, err)
// check if tracking is updated accordingly
lockupAccountInfoResponse := s.queryLockupAccInfo(ctx, app, accountAddr)
delLocking := lockupAccountInfoResponse.DelegatedLocking
require.True(t, delLocking.AmountOf("stake").Equal(math.ZeroInt()))
})


t.Run("ok - execute delegate message", func(t *testing.T) {
msg := &types.MsgDelegate{
Sender: ownerAddrStr,
Expand Down
29 changes: 25 additions & 4 deletions tests/e2e/accounts/lockup/permanent_lockup_test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (s *E2ETestSuite) TestPermanentLockingAccount() {
ctx := sdk.NewContext(app.CommitMultiStore(), false, app.Logger()).WithHeaderInfo(header.Info{
Time: currentTime,
})
s.setupStakingParams(ctx, app)
ownerAddrStr, err := app.AuthKeeper.AddressCodec().BytesToString(accOwner)
require.NoError(t, err)
s.fundAccount(app, ctx, accOwner, sdk.Coins{sdk.NewCoin("stake", math.NewInt(1000000))})
Expand Down Expand Up @@ -109,10 +110,11 @@ func (s *E2ETestSuite) TestPermanentLockingAccount() {
require.NoError(t, err)
require.Equal(t, len(ubd.Entries), 1)

// check if tracking is updated accordingly
lockupAccountInfoResponse := s.queryLockupAccInfo(ctx, app, accountAddr)
delLocking := lockupAccountInfoResponse.DelegatedLocking
require.True(t, delLocking.AmountOf("stake").Equal(math.ZeroInt()))
// check if an entry is added
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use require.Equal for clearer assertions

Using require.Equal instead of require.True with an equality condition provides more informative error messages if the test fails.

Apply this diff:

-		require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
+		require.Equal(t, val.OperatorAddress, entries[0].ValidatorAddress)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.Equal(t, val.OperatorAddress, entries[0].ValidatorAddress)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Check for empty unbonding entries before accessing

Accessing entries[0] without verifying that entries is not empty could lead to an index out of range error if no unbonding entries are present. Please add a check to ensure entries contains at least one element before accessing it.

Apply this diff to add the length check:

+		require.Greater(t, len(entries), 0)
		require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
		require.Equal(t, val.OperatorAddress, entries[0].ValidatorAddress)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// check if an entry is added
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.True(t, entries[0].ValidatorAddress == val.OperatorAddress)
// check if an entry is added
unbondingEntriesResponse := s.queryUnbondingEntries(ctx, app, accountAddr)
entries := unbondingEntriesResponse.UnbondingEntries
require.Greater(t, len(entries), 0)
require.True(t, entries[0].Amount.Amount.Equal(math.NewInt(100)))
require.Equal(t, val.OperatorAddress, entries[0].ValidatorAddress)

})

s.fundAccount(app, ctx, accountAddr, sdk.Coins{sdk.NewCoin("stake", math.NewInt(1000))})
Expand All @@ -129,4 +131,23 @@ func (s *E2ETestSuite) TestPermanentLockingAccount() {
balance := app.BankKeeper.GetBalance(ctx, randAcc, "stake")
require.True(t, balance.Amount.Equal(math.NewInt(100)))
})

// Update context time
ctx = ctx.WithHeaderInfo(header.Info{
Time: currentTime.Add(time.Second * 11),
})

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Unbonding Time Discrepancies Detected

The unbonding periods across the codebase vary and do not consistently align with the test's assumption of a maximum 10-second unbonding period. This may lead to tests failing if unbonding entries have not matured as expected.

  • Files with varying UnbondingTime:
    • testutil/sims/app_helpers.go
    • tests/integration/tx/aminojson/aminojson_test.go
    • tests/integration/staking/keeper/deterministic_test.go
    • tests/integration/staking/keeper/genesis_test.go
    • x/staking/keeper/keeper_test.go
    • x/staking/keeper/msg_server_test.go
    • x/params/types/subspace_test.go
    • x/staking/types/validator.go
    • x/staking/types/params.go
🔗 Analysis chain

Verify unbonding period before advancing context time

Advancing the context time by 11 seconds assumes that the unbonding period is less than or equal to 10 seconds. Please verify that the unbonding period set in the staking parameters matches this assumption to ensure unbonding entries have matured.

Run the following script to confirm the unbonding time:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Retrieve the unbonding time from the staking parameters to verify it aligns with the test's time advancement.

# Command:
ast-grep --lang go --pattern 'UnbondingTime: $time' --json 'app.StakingKeeper.SetParams(ctx, stakingtypes.Params{ $$$ })' | jq '.[].meta.captures.time'

Length of output: 261


Script:

#!/bin/bash
# Description: Search for the UnbondingTime parameter in the staking parameters setup to verify its value.

# Using ripgrep to find lines where UnbondingTime is set
rg 'UnbondingTime\s*:\s*\w+' --files-with-matches | xargs rg 'UnbondingTime\s*:\s*(\w+)'

Length of output: 1912

t.Run("ok - execute tracking unbonding entry", func(t *testing.T) {
msg := &types.MsgTrackUndelegation{
Sender: ownerAddrStr,
Id: 0,
}
err = s.executeTx(ctx, msg, app, accountAddr, accOwner)
require.NoError(t, err)

// check if tracking is updated accordingly
lockupAccountInfoResponse := s.queryLockupAccInfo(ctx, app, accountAddr)
delLocking := lockupAccountInfoResponse.DelegatedLocking
require.True(t, delLocking.AmountOf("stake").Equal(math.ZeroInt()))
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add test cases for multiple unbonding entries

Currently, the test tracks undelegation for a single entry with Id: 0. To ensure comprehensive coverage, consider adding test cases that handle multiple unbonding entries and verify that tracking works correctly for each.

}
23 changes: 23 additions & 0 deletions tests/e2e/accounts/lockup/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lockup

import (
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
Expand Down Expand Up @@ -70,3 +71,25 @@ func (s *E2ETestSuite) queryLockupAccInfo(ctx sdk.Context, app *simapp.SimApp, a

return lockupAccountInfoResponse
}

func (s *E2ETestSuite) queryUnbondingEntries(ctx sdk.Context, app *simapp.SimApp, accAddr []byte) *types.QueryUnbondingEntriesResponse {
req := &types.QueryUnbondingEntriesRequest{}
resp, err := s.queryAcc(ctx, req, app, accAddr)
require.NoError(s.T(), err)
require.NotNil(s.T(), resp)

unbondingEntriesResponse, ok := resp.(*types.QueryUnbondingEntriesResponse)
require.True(s.T(), ok)

return unbondingEntriesResponse
}

func (s *E2ETestSuite) setupStakingParams(ctx sdk.Context, app *simapp.SimApp) {
params, err := app.StakingKeeper.Params.Get(ctx)
require.NoError(s.T(), err)

// update unbonding time
params.UnbondingTime = time.Duration(time.Second * 10)
err = app.StakingKeeper.Params.Set(ctx, params)
require.NoError(s.T(), err)
}
1 change: 1 addition & 0 deletions x/accounts/defaults/lockup/continuous_locking_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,5 @@ func (cva ContinuousLockingAccount) RegisterExecuteHandlers(builder *accountstd.

func (cva ContinuousLockingAccount) RegisterQueryHandlers(builder *accountstd.QueryBuilder) {
accountstd.RegisterQueryHandler(builder, cva.QueryLockupAccountInfo)
accountstd.RegisterQueryHandler(builder, cva.QueryUnbondingEntries)
sontrinh16 marked this conversation as resolved.
Show resolved Hide resolved
}
43 changes: 9 additions & 34 deletions x/accounts/defaults/lockup/continuous_locking_account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,48 +102,23 @@ func TestContinousAccountUndelegate(t *testing.T) {
})
require.NoError(t, err)

delLocking, err = acc.DelegatedLocking.Get(ctx, "test")
require.NoError(t, err)
require.True(t, delLocking.Equal(math.ZeroInt()))

startTime, err := acc.StartTime.Get(sdkCtx)
require.NoError(t, err)

// Update context time to unlocked half of the original locking amount
sdkCtx = sdkCtx.WithHeaderInfo(header.Info{
Time: startTime.Add(time.Minute * 1),
})

_, err = acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{
Sender: "owner",
ValidatorAddress: "val_address",
Amount: sdk.NewCoin("test", math.NewInt(6)),
})
require.NoError(t, err)

delLocking, err = acc.DelegatedLocking.Get(ctx, "test")
ubdSeq, err := acc.UnbondingSequence.Peek(sdkCtx)
require.NoError(t, err)
require.True(t, delLocking.Equal(math.NewInt(5)))

delFree, err := acc.DelegatedFree.Get(ctx, "test")
// sequence should be the previous one
ubdEntry, err := acc.UnbondEntries.Get(sdkCtx, ubdSeq-1)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent potential underflow when accessing unbonding entries

When subtracting 1 from ubdSeq, there's a risk of underflow if ubdSeq is zero, leading to a negative index and a possible runtime error. To prevent this, ensure that ubdSeq is greater than zero before performing the subtraction.

Consider adding a check to confirm ubdSeq is positive:

 ubdSeq, err := acc.UnbondingSequence.Peek(sdkCtx)
 require.NoError(t, err)
+require.True(t, ubdSeq > 0, "Unbonding sequence must be greater than zero")
 // sequence should be the previous one
 ubdEntry, err := acc.UnbondEntries.Get(sdkCtx, ubdSeq-1)
 require.NoError(t, err)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ubdSeq, err := acc.UnbondingSequence.Peek(sdkCtx)
require.NoError(t, err)
require.True(t, delLocking.Equal(math.NewInt(5)))
delFree, err := acc.DelegatedFree.Get(ctx, "test")
// sequence should be the previous one
ubdEntry, err := acc.UnbondEntries.Get(sdkCtx, ubdSeq-1)
ubdSeq, err := acc.UnbondingSequence.Peek(sdkCtx)
require.NoError(t, err)
require.True(t, ubdSeq > 0, "Unbonding sequence must be greater than zero")
// sequence should be the previous one
ubdEntry, err := acc.UnbondEntries.Get(sdkCtx, ubdSeq-1)

require.NoError(t, err)
require.True(t, delFree.Equal(math.NewInt(1)))
require.True(t, ubdEntry.Amount.Amount.Equal(math.NewInt(1)))
require.True(t, ubdEntry.ValidatorAddress == "val_address")

// Undelegate
_, err = acc.Undelegate(sdkCtx, &lockuptypes.MsgUndelegate{
Sender: "owner",
ValidatorAddress: "val_address",
Amount: sdk.NewCoin("test", math.NewInt(4)),
_, err = acc.TrackUndelegationEntry(sdkCtx, &lockuptypes.MsgTrackUndelegation{
Sender: "owner",
Id: ubdSeq - 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure valid unbonding entry ID before tracking undelegation

Similar to the previous issue, when passing ubdSeq - 1 as the Id in TrackUndelegationEntry, verify that ubdSeq is greater than zero to avoid underflow and invalid IDs.

Add a check before calling TrackUndelegationEntry:

+require.True(t, ubdSeq > 0, "Unbonding sequence must be greater than zero")
 _, err = acc.TrackUndelegationEntry(sdkCtx, &lockuptypes.MsgTrackUndelegation{
     Sender: "owner",
     Id:     ubdSeq - 1,
 })
 require.NoError(t, err)

Committable suggestion was skipped due to low confidence.

})
require.NoError(t, err)

delLocking, err = acc.DelegatedLocking.Get(ctx, "test")
require.NoError(t, err)
require.True(t, delLocking.Equal(math.NewInt(2)))

delFree, err = acc.DelegatedFree.Get(ctx, "test")
require.NoError(t, err)
require.True(t, delFree.Equal(math.ZeroInt()))
require.True(t, delLocking.Equal(math.ZeroInt()))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add timeline validation for undelegation process

The test should verify the intermediate state where funds are still locked during the unbonding period. Add assertions to check:

  1. Delegated locking amount before unbonding maturity
  2. Delegated locking amount after advancing context time to maturity

Example:

// Check intermediate state
delLocking, err = acc.DelegatedLocking.Get(ctx, "test")
require.NoError(t, err)
require.True(t, delLocking.Equal(math.NewInt(1))) // Should still be locked

// Advance time to maturity
sdkCtx = sdkCtx.WithHeaderInfo(header.Info{
    Time: ubdEntry.CompletionTime,
})

// Update undelegation entry after maturity
_, err = acc.UpdateUndelegationEntry(sdkCtx, &lockuptypes.MsgUpdateUndelegationEntry{
    Sender: "owner",
    Id:     ubdSeq - 1,
})
require.NoError(t, err)

// Now verify zero locking amount
delLocking, err = acc.DelegatedLocking.Get(ctx, "test")
require.NoError(t, err)
require.True(t, delLocking.Equal(math.ZeroInt()))

}

func TestContinousAccountSendCoins(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions x/accounts/defaults/lockup/delayed_locking_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,5 @@ func (dva DelayedLockingAccount) RegisterExecuteHandlers(builder *accountstd.Exe

func (dva DelayedLockingAccount) RegisterQueryHandlers(builder *accountstd.QueryBuilder) {
accountstd.RegisterQueryHandler(builder, dva.QueryVestingAccountInfo)
accountstd.RegisterQueryHandler(builder, dva.QueryUnbondingEntries)
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

🔍 Missing Implementation of QueryUnbondingEntries Method

The QueryUnbondingEntries query handler has been registered, but the implementation of the QueryUnbondingEntries method was not found in the codebase.

  • Ensure that the QueryUnbondingEntries method is implemented within the DelayedLockingAccount struct.
  • Verify that the method adheres to the expected signature and correctly handles the request and response types.
🔗 Analysis chain

LGTM. Verify the implementation of QueryUnbondingEntries.

The addition of the QueryUnbondingEntries query handler is consistent with the PR objectives and follows the established pattern for registering query handlers. However, the implementation of this method is not visible in the current file.

Please run the following script to verify the existence and implementation of the QueryUnbondingEntries method:

If the method is not found or the types are not defined, please implement them to ensure the newly added query handler functions correctly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of QueryUnbondingEntries method

# Test: Search for the QueryUnbondingEntries method implementation
ast-grep --lang go --pattern $'func (dva DelayedLockingAccount) QueryUnbondingEntries(ctx context.Context, req *lockuptypes.QueryUnbondingEntriesRequest) (*lockuptypes.QueryUnbondingEntriesResponse, error) {
  $$$
}'

# Test: Check if the QueryUnbondingEntriesRequest and QueryUnbondingEntriesResponse types are defined
rg --type go -e 'type QueryUnbondingEntriesRequest struct' -e 'type QueryUnbondingEntriesResponse struct'

Length of output: 693

}
37 changes: 8 additions & 29 deletions x/accounts/defaults/lockup/delayed_locking_account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,44 +101,23 @@ func TestDelayedAccountUndelegate(t *testing.T) {
})
require.NoError(t, err)

delLocking, err = acc.DelegatedLocking.Get(ctx, "test")
ubdSeq, err := acc.UnbondingSequence.Peek(sdkCtx)
require.NoError(t, err)
require.True(t, delLocking.Equal(math.ZeroInt()))

endTime, err := acc.EndTime.Get(sdkCtx)
// sequence should be the previous one
ubdEntry, err := acc.UnbondEntries.Get(sdkCtx, ubdSeq-1)
require.NoError(t, err)
require.True(t, ubdEntry.Amount.Amount.Equal(math.NewInt(1)))
require.True(t, ubdEntry.ValidatorAddress == "val_address")

// Update context time to unlocked all the original locking amount
sdkCtx = sdkCtx.WithHeaderInfo(header.Info{
Time: endTime.Add(time.Second),
})

_, err = acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{
Sender: "owner",
ValidatorAddress: "val_address",
Amount: sdk.NewCoin("test", math.NewInt(6)),
_, err = acc.TrackUndelegationEntry(sdkCtx, &lockuptypes.MsgTrackUndelegation{
Sender: "owner",
Id: ubdSeq - 1,
})
require.NoError(t, err)

delLocking, err = acc.DelegatedLocking.Get(ctx, "test")
require.NoError(t, err)
require.True(t, delLocking.Equal(math.ZeroInt()))

delFree, err := acc.DelegatedFree.Get(ctx, "test")
require.NoError(t, err)
require.True(t, delFree.Equal(math.NewInt(6)))

// Undelegate
_, err = acc.Undelegate(sdkCtx, &lockuptypes.MsgUndelegate{
Sender: "owner",
ValidatorAddress: "val_address",
Amount: sdk.NewCoin("test", math.NewInt(4)),
})
require.NoError(t, err)

delFree, err = acc.DelegatedFree.Get(ctx, "test")
require.NoError(t, err)
require.True(t, delFree.Equal(math.NewInt(2)))
}

func TestDelayedAccountSendCoins(t *testing.T) {
Expand Down
Loading
Loading