From c34988508437341a1f809f7b5ff9378e35b56b95 Mon Sep 17 00:00:00 2001 From: son trinh Date: Tue, 9 Jul 2024 16:38:40 +0700 Subject: [PATCH] refactor(x/accounts/defaults/lockup): Add lockup unit tests (#20721) --- .../lockup/continuous_locking_account_test.go | 235 +++++++++++++++ .../lockup/delayed_locking_account_test.go | 224 ++++++++++++++ x/accounts/defaults/lockup/go.mod | 4 +- x/accounts/defaults/lockup/lockup_test.go | 4 +- .../lockup/periodic_locking_account_test.go | 274 ++++++++++++++++++ .../lockup/permanent_locking_account.go | 3 +- .../lockup/permanent_locking_account_test.go | 97 +++++++ x/accounts/defaults/lockup/utils_test.go | 52 +++- 8 files changed, 880 insertions(+), 13 deletions(-) create mode 100644 x/accounts/defaults/lockup/continuous_locking_account_test.go create mode 100644 x/accounts/defaults/lockup/delayed_locking_account_test.go create mode 100644 x/accounts/defaults/lockup/periodic_locking_account_test.go create mode 100644 x/accounts/defaults/lockup/permanent_locking_account_test.go diff --git a/x/accounts/defaults/lockup/continuous_locking_account_test.go b/x/accounts/defaults/lockup/continuous_locking_account_test.go new file mode 100644 index 0000000000..1c5742833a --- /dev/null +++ b/x/accounts/defaults/lockup/continuous_locking_account_test.go @@ -0,0 +1,235 @@ +package lockup + +import ( + "context" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "cosmossdk.io/core/header" + "cosmossdk.io/core/store" + "cosmossdk.io/log" + "cosmossdk.io/math" + lockuptypes "cosmossdk.io/x/accounts/defaults/lockup/types" +) + +func setupContinousAccount(t *testing.T, ctx context.Context, ss store.KVStoreService) *ContinuousLockingAccount { + t.Helper() + deps := makeMockDependencies(ss) + owner := "owner" + + acc, err := NewContinuousLockingAccount(deps) + require.NoError(t, err) + _, err = acc.Init(ctx, &lockuptypes.MsgInitLockupAccount{ + Owner: owner, + EndTime: time.Now().Add(time.Minute * 2), + StartTime: time.Now(), + }) + require.NoError(t, err) + + return acc +} + +func TestContinousAccountDelegate(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupContinousAccount(t, sdkCtx, ss) + _, err := acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + require.NoError(t, err) + + delLocking, err := acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(1))) + + 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(5)), + }) + require.NoError(t, err) + + delLocking, err = acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(5))) + + delFree, err := acc.DelegatedFree.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delFree.Equal(math.NewInt(1))) +} + +func TestContinousAccountUndelegate(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupContinousAccount(t, sdkCtx, ss) + // Delegate first + _, err := acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + require.NoError(t, err) + + delLocking, err := acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(1))) + + // Undelegate + _, err = acc.Undelegate(sdkCtx, &lockuptypes.MsgUndelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + 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") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(5))) + + delFree, err := acc.DelegatedFree.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delFree.Equal(math.NewInt(1))) + + // Undelegate + _, err = acc.Undelegate(sdkCtx, &lockuptypes.MsgUndelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(4)), + }) + 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())) +} + +func TestContinousAccountSendCoins(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupContinousAccount(t, sdkCtx, ss) + _, err := acc.SendCoins(sdkCtx, &lockuptypes.MsgSend{ + Sender: "owner", + ToAddress: "receiver", + Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(5))), + }) + require.Error(t, err) + + 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.SendCoins(sdkCtx, &lockuptypes.MsgSend{ + Sender: "owner", + ToAddress: "receiver", + Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(5))), + }) + require.NoError(t, err) +} + +func TestContinousAccountWithdrawUnlockedCoins(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupContinousAccount(t, sdkCtx, ss) + _, err := acc.WithdrawUnlockedCoins(sdkCtx, &lockuptypes.MsgWithdraw{ + Withdrawer: "owner", + ToAddress: "receiver", + Denoms: []string{"test"}, + }) + require.Error(t, err) + + 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.WithdrawUnlockedCoins(sdkCtx, &lockuptypes.MsgWithdraw{ + Withdrawer: "owner", + ToAddress: "receiver", + Denoms: []string{"test"}, + }) + require.NoError(t, err) +} + +func TestContinousAccountGetLockCoinInfo(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupContinousAccount(t, sdkCtx, ss) + + unlocked, locked, err := acc.GetLockCoinsInfo(sdkCtx, time.Now()) + require.NoError(t, err) + require.True(t, unlocked.AmountOf("test").Equal(math.ZeroInt())) + require.True(t, locked.AmountOf("test").Equal(math.NewInt(10))) + + startTime, err := acc.StartTime.Get(sdkCtx) + require.NoError(t, err) + + // unlocked half locked token + unlocked, locked, err = acc.GetLockCoinsInfo(sdkCtx, startTime.Add(time.Minute*1)) + require.NoError(t, err) + require.True(t, unlocked.AmountOf("test").Equal(math.NewInt(5))) + require.True(t, locked.AmountOf("test").Equal(math.NewInt(5))) + + // unlocked full locked token + unlocked, locked, err = acc.GetLockCoinsInfo(sdkCtx, startTime.Add(time.Minute*2)) + require.NoError(t, err) + require.True(t, unlocked.AmountOf("test").Equal(math.NewInt(10))) + require.True(t, locked.AmountOf("test").Equal(math.ZeroInt())) +} diff --git a/x/accounts/defaults/lockup/delayed_locking_account_test.go b/x/accounts/defaults/lockup/delayed_locking_account_test.go new file mode 100644 index 0000000000..2a9fa85b75 --- /dev/null +++ b/x/accounts/defaults/lockup/delayed_locking_account_test.go @@ -0,0 +1,224 @@ +package lockup + +import ( + "context" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "cosmossdk.io/core/header" + "cosmossdk.io/core/store" + "cosmossdk.io/log" + "cosmossdk.io/math" + lockuptypes "cosmossdk.io/x/accounts/defaults/lockup/types" +) + +func setupDelayedAccount(t *testing.T, ctx context.Context, ss store.KVStoreService) *DelayedLockingAccount { + t.Helper() + deps := makeMockDependencies(ss) + owner := "owner" + + acc, err := NewDelayedLockingAccount(deps) + require.NoError(t, err) + _, err = acc.Init(ctx, &lockuptypes.MsgInitLockupAccount{ + Owner: owner, + EndTime: time.Now().Add(time.Minute * 2), + }) + require.NoError(t, err) + + return acc +} + +func TestDelayedAccountDelegate(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupDelayedAccount(t, sdkCtx, ss) + _, err := acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + require.NoError(t, err) + + delLocking, err := acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(1))) + + endTime, err := acc.EndTime.Get(sdkCtx) + require.NoError(t, err) + + // 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(5)), + }) + require.NoError(t, err) + + delLocking, err = acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(1))) + + delFree, err := acc.DelegatedFree.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delFree.Equal(math.NewInt(5))) +} + +func TestDelayedAccountUndelegate(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupDelayedAccount(t, sdkCtx, ss) + // Delegate first + _, err := acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + require.NoError(t, err) + + delLocking, err := acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(1))) + + // Undelegate + _, err = acc.Undelegate(sdkCtx, &lockuptypes.MsgUndelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + require.NoError(t, err) + + delLocking, err = acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.ZeroInt())) + + endTime, err := acc.EndTime.Get(sdkCtx) + require.NoError(t, err) + + // 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)), + }) + 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) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupDelayedAccount(t, sdkCtx, ss) + _, err := acc.SendCoins(sdkCtx, &lockuptypes.MsgSend{ + Sender: "owner", + ToAddress: "receiver", + Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(5))), + }) + require.Error(t, err) + + endTime, err := acc.EndTime.Get(sdkCtx) + require.NoError(t, err) + + // Update context time to unlocked all the original locking amount + sdkCtx = sdkCtx.WithHeaderInfo(header.Info{ + Time: endTime.Add(time.Second), + }) + + _, err = acc.SendCoins(sdkCtx, &lockuptypes.MsgSend{ + Sender: "owner", + ToAddress: "receiver", + Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(5))), + }) + require.NoError(t, err) +} + +func TestDelayedAccountWithdrawUnlockedCoins(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupDelayedAccount(t, sdkCtx, ss) + _, err := acc.WithdrawUnlockedCoins(sdkCtx, &lockuptypes.MsgWithdraw{ + Withdrawer: "owner", + ToAddress: "receiver", + Denoms: []string{"test"}, + }) + require.Error(t, err) + + endTime, err := acc.EndTime.Get(sdkCtx) + require.NoError(t, err) + + // Update context time to unlocked all the original locking amount + sdkCtx = sdkCtx.WithHeaderInfo(header.Info{ + Time: endTime.Add(time.Second), + }) + + _, err = acc.WithdrawUnlockedCoins(sdkCtx, &lockuptypes.MsgWithdraw{ + Withdrawer: "owner", + ToAddress: "receiver", + Denoms: []string{"test"}, + }) + require.NoError(t, err) +} + +func TestDelayedAccountGetLockCoinInfo(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupDelayedAccount(t, sdkCtx, ss) + + unlocked, locked, err := acc.GetLockCoinsInfo(sdkCtx, time.Now()) + require.NoError(t, err) + require.True(t, unlocked.AmountOf("test").Equal(math.ZeroInt())) + require.True(t, locked.AmountOf("test").Equal(math.NewInt(10))) + + endTime, err := acc.EndTime.Get(sdkCtx) + require.NoError(t, err) + + // unlocked full locked token + unlocked, locked, err = acc.GetLockCoinsInfo(sdkCtx, endTime.Add(time.Second*1)) + require.NoError(t, err) + require.True(t, unlocked.AmountOf("test").Equal(math.NewInt(10))) + require.True(t, locked.AmountOf("test").Equal(math.ZeroInt())) +} diff --git a/x/accounts/defaults/lockup/go.mod b/x/accounts/defaults/lockup/go.mod index 5883327445..c8789e9512 100644 --- a/x/accounts/defaults/lockup/go.mod +++ b/x/accounts/defaults/lockup/go.mod @@ -33,7 +33,7 @@ require ( buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.34.2-20240130113600-88ef6483f90f.2 // indirect cosmossdk.io/api v0.7.5 // indirect cosmossdk.io/errors v1.0.1 - cosmossdk.io/log v1.3.1 // indirect + cosmossdk.io/log v1.3.1 cosmossdk.io/math v1.3.0 cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc // indirect cosmossdk.io/x/auth v0.0.0-00010101000000-000000000000 // indirect @@ -81,7 +81,7 @@ require ( github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.2.0 // indirect - github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/protobuf v1.5.4 github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect diff --git a/x/accounts/defaults/lockup/lockup_test.go b/x/accounts/defaults/lockup/lockup_test.go index c76b16bf88..67f3cadfbd 100644 --- a/x/accounts/defaults/lockup/lockup_test.go +++ b/x/accounts/defaults/lockup/lockup_test.go @@ -273,6 +273,8 @@ func TestQueryLockupAccountBaseInfo(t *testing.T) { baseLockup := setup(t, ctx, ss) - _, err := baseLockup.QueryLockupAccountBaseInfo(ctx, &lockuptypes.QueryLockupAccountInfoRequest{}) + res, err := baseLockup.QueryLockupAccountBaseInfo(ctx, &lockuptypes.QueryLockupAccountInfoRequest{}) + require.Equal(t, res.OriginalLocking.AmountOf("test"), math.NewInt(10)) + require.Equal(t, res.Owner, "owner") require.NoError(t, err) } diff --git a/x/accounts/defaults/lockup/periodic_locking_account_test.go b/x/accounts/defaults/lockup/periodic_locking_account_test.go new file mode 100644 index 0000000000..e05b033430 --- /dev/null +++ b/x/accounts/defaults/lockup/periodic_locking_account_test.go @@ -0,0 +1,274 @@ +package lockup + +import ( + "context" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "cosmossdk.io/core/header" + "cosmossdk.io/core/store" + "cosmossdk.io/log" + "cosmossdk.io/math" + lockuptypes "cosmossdk.io/x/accounts/defaults/lockup/types" +) + +func setupPeriodicAccount(t *testing.T, ctx context.Context, ss store.KVStoreService) *PeriodicLockingAccount { + t.Helper() + deps := makeMockDependencies(ss) + owner := "owner" + + acc, err := NewPeriodicLockingAccount(deps) + require.NoError(t, err) + _, err = acc.Init(ctx, &lockuptypes.MsgInitPeriodicLockingAccount{ + Owner: owner, + StartTime: time.Now(), + LockingPeriods: []lockuptypes.Period{ + { + Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(5))), + Length: time.Minute, + }, + { + Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(2))), + Length: time.Minute, + }, + { + Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(3))), + Length: time.Minute, + }, + }, + }) + require.NoError(t, err) + + return acc +} + +func TestPeriodicAccountDelegate(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupPeriodicAccount(t, sdkCtx, ss) + _, err := acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + require.NoError(t, err) + + delLocking, err := acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(1))) + + startTime, err := acc.StartTime.Get(sdkCtx) + require.NoError(t, err) + + // Update context time to unlocked first period token + 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(5)), + }) + require.NoError(t, err) + + delLocking, err = acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(5))) + + delFree, err := acc.DelegatedFree.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delFree.Equal(math.NewInt(1))) + + // Update context time to unlocked all token + sdkCtx = sdkCtx.WithHeaderInfo(header.Info{ + Time: startTime.Add(time.Minute * 3), + }) + + _, err = acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(4)), + }) + require.NoError(t, err) + + delLocking, err = acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(5))) + + delFree, err = acc.DelegatedFree.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delFree.Equal(math.NewInt(5))) +} + +func TestPeriodicAccountUndelegate(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupPeriodicAccount(t, sdkCtx, ss) + // Delegate first + _, err := acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + require.NoError(t, err) + + delLocking, err := acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(1))) + + // Undelegate + _, err = acc.Undelegate(sdkCtx, &lockuptypes.MsgUndelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + 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 first period token + 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") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(5))) + + delFree, err := acc.DelegatedFree.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delFree.Equal(math.NewInt(1))) + + // Undelegate + _, err = acc.Undelegate(sdkCtx, &lockuptypes.MsgUndelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(4)), + }) + 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())) +} + +func TestPeriodicAccountSendCoins(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupPeriodicAccount(t, sdkCtx, ss) + _, err := acc.SendCoins(sdkCtx, &lockuptypes.MsgSend{ + Sender: "owner", + ToAddress: "receiver", + Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(5))), + }) + require.Error(t, err) + + startTime, err := acc.StartTime.Get(sdkCtx) + require.NoError(t, err) + + // Update context time to unlocked first period token + sdkCtx = sdkCtx.WithHeaderInfo(header.Info{ + Time: startTime.Add(time.Minute * 1), + }) + + _, err = acc.SendCoins(sdkCtx, &lockuptypes.MsgSend{ + Sender: "owner", + ToAddress: "receiver", + Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(5))), + }) + require.NoError(t, err) +} + +func TestPeriodicAccountWithdrawUnlockedCoins(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupPeriodicAccount(t, sdkCtx, ss) + _, err := acc.WithdrawUnlockedCoins(sdkCtx, &lockuptypes.MsgWithdraw{ + Withdrawer: "owner", + ToAddress: "receiver", + Denoms: []string{"test"}, + }) + require.Error(t, err) + + startTime, err := acc.StartTime.Get(sdkCtx) + require.NoError(t, err) + + // Update context time to unlocked first period token + sdkCtx = sdkCtx.WithHeaderInfo(header.Info{ + Time: startTime.Add(time.Minute * 1), + }) + + _, err = acc.WithdrawUnlockedCoins(sdkCtx, &lockuptypes.MsgWithdraw{ + Withdrawer: "owner", + ToAddress: "receiver", + Denoms: []string{"test"}, + }) + require.NoError(t, err) +} + +func TestPeriodicAccountGetLockCoinInfo(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupPeriodicAccount(t, sdkCtx, ss) + + unlocked, locked, err := acc.GetLockCoinsInfo(sdkCtx, time.Now()) + require.NoError(t, err) + require.True(t, unlocked.AmountOf("test").Equal(math.ZeroInt())) + require.True(t, locked.AmountOf("test").Equal(math.NewInt(10))) + + startTime, err := acc.StartTime.Get(sdkCtx) + require.NoError(t, err) + + // unlocked first period locked token + unlocked, locked, err = acc.GetLockCoinsInfo(sdkCtx, startTime.Add(time.Minute*1)) + require.NoError(t, err) + require.True(t, unlocked.AmountOf("test").Equal(math.NewInt(5))) + require.True(t, locked.AmountOf("test").Equal(math.NewInt(5))) + + // unlocked second period locked token + unlocked, locked, err = acc.GetLockCoinsInfo(sdkCtx, startTime.Add(time.Minute*2)) + require.NoError(t, err) + require.True(t, unlocked.AmountOf("test").Equal(math.NewInt(7))) + require.True(t, locked.AmountOf("test").Equal(math.NewInt(3))) + + // unlocked third period locked token + unlocked, locked, err = acc.GetLockCoinsInfo(sdkCtx, startTime.Add(time.Minute*3)) + require.NoError(t, err) + require.True(t, unlocked.AmountOf("test").Equal(math.NewInt(10))) + require.True(t, locked.AmountOf("test").Equal(math.ZeroInt())) +} diff --git a/x/accounts/defaults/lockup/permanent_locking_account.go b/x/accounts/defaults/lockup/permanent_locking_account.go index 2ca615c15b..5e5d0dab98 100644 --- a/x/accounts/defaults/lockup/permanent_locking_account.go +++ b/x/accounts/defaults/lockup/permanent_locking_account.go @@ -100,8 +100,9 @@ func (plva PermanentLockingAccount) RegisterInitHandler(builder *accountstd.Init func (plva PermanentLockingAccount) RegisterExecuteHandlers(builder *accountstd.ExecuteBuilder) { accountstd.RegisterExecuteHandler(builder, plva.Delegate) + accountstd.RegisterExecuteHandler(builder, plva.Undelegate) accountstd.RegisterExecuteHandler(builder, plva.SendCoins) - plva.BaseLockup.RegisterExecuteHandlers(builder) + accountstd.RegisterExecuteHandler(builder, plva.WithdrawReward) } func (plva PermanentLockingAccount) RegisterQueryHandlers(builder *accountstd.QueryBuilder) { diff --git a/x/accounts/defaults/lockup/permanent_locking_account_test.go b/x/accounts/defaults/lockup/permanent_locking_account_test.go new file mode 100644 index 0000000000..341cd3c681 --- /dev/null +++ b/x/accounts/defaults/lockup/permanent_locking_account_test.go @@ -0,0 +1,97 @@ +package lockup + +import ( + "context" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "cosmossdk.io/core/header" + "cosmossdk.io/core/store" + "cosmossdk.io/log" + "cosmossdk.io/math" + lockuptypes "cosmossdk.io/x/accounts/defaults/lockup/types" +) + +func setupPermanentAccount(t *testing.T, ctx context.Context, ss store.KVStoreService) *PermanentLockingAccount { + t.Helper() + deps := makeMockDependencies(ss) + owner := "owner" + + acc, err := NewPermanentLockingAccount(deps) + require.NoError(t, err) + _, err = acc.Init(ctx, &lockuptypes.MsgInitLockupAccount{ + Owner: owner, + }) + require.NoError(t, err) + + return acc +} + +func TestPermanentAccountDelegate(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupPermanentAccount(t, sdkCtx, ss) + _, err := acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + require.NoError(t, err) + + delLocking, err := acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(1))) +} + +func TestPermanentAccountUndelegate(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupPermanentAccount(t, sdkCtx, ss) + // Delegate first + _, err := acc.Delegate(sdkCtx, &lockuptypes.MsgDelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + require.NoError(t, err) + + delLocking, err := acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.NewInt(1))) + + // Undelegate + _, err = acc.Undelegate(sdkCtx, &lockuptypes.MsgUndelegate{ + Sender: "owner", + ValidatorAddress: "val_address", + Amount: sdk.NewCoin("test", math.NewInt(1)), + }) + require.NoError(t, err) + + delLocking, err = acc.DelegatedLocking.Get(ctx, "test") + require.NoError(t, err) + require.True(t, delLocking.Equal(math.ZeroInt())) +} + +func TestPermanentAccountSendCoins(t *testing.T) { + ctx, ss := newMockContext(t) + sdkCtx := sdk.NewContext(nil, true, log.NewNopLogger()).WithContext(ctx).WithHeaderInfo(header.Info{ + Time: time.Now(), + }) + + acc := setupPermanentAccount(t, sdkCtx, ss) + _, err := acc.SendCoins(sdkCtx, &lockuptypes.MsgSend{ + Sender: "owner", + ToAddress: "receiver", + Amount: sdk.NewCoins(sdk.NewCoin("test", math.NewInt(5))), + }) + require.Error(t, err) +} diff --git a/x/accounts/defaults/lockup/utils_test.go b/x/accounts/defaults/lockup/utils_test.go index 6dd435512e..56cd3316ed 100644 --- a/x/accounts/defaults/lockup/utils_test.go +++ b/x/accounts/defaults/lockup/utils_test.go @@ -2,19 +2,25 @@ package lockup import ( "context" + "fmt" "testing" gogoproto "github.com/cosmos/gogoproto/proto" + "github.com/golang/protobuf/proto" // nolint: staticcheck // needed because gogoproto.Merge does not work consistently. See NOTE: comments. "github.com/stretchr/testify/require" "google.golang.org/protobuf/runtime/protoiface" "cosmossdk.io/collections" + "cosmossdk.io/core/appmodule/v2" + "cosmossdk.io/core/header" "cosmossdk.io/core/store" "cosmossdk.io/math" "cosmossdk.io/x/accounts/accountstd" banktypes "cosmossdk.io/x/bank/types" + distrtypes "cosmossdk.io/x/distribution/types" stakingtypes "cosmossdk.io/x/staking/types" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -23,7 +29,11 @@ type ProtoMsg = protoiface.MessageV1 var TestFunds = sdk.NewCoins(sdk.NewCoin("test", math.NewInt(10))) // mock statecodec -type mockStateCodec struct{} +type mockStateCodec struct { + codec.Codec +} + +var _ codec.Codec = mockStateCodec{} func (c mockStateCodec) Marshal(m gogoproto.Message) ([]byte, error) { // Size() check can catch the typed nil value. @@ -53,13 +63,32 @@ type addressCodec struct{} func (a addressCodec) StringToBytes(text string) ([]byte, error) { return []byte(text), nil } func (a addressCodec) BytesToString(bz []byte) (string, error) { return string(bz), nil } +// mock header service +type headerService struct{} + +func (h headerService) HeaderInfo(ctx context.Context) header.Info { + return sdk.UnwrapSDKContext(ctx).HeaderInfo() +} + func newMockContext(t *testing.T) (context.Context, store.KVStoreService) { t.Helper() return accountstd.NewMockContext( 0, []byte("lockup_account"), []byte("sender"), TestFunds, func(ctx context.Context, sender []byte, msg, msgResp ProtoMsg) error { return nil }, func(ctx context.Context, sender []byte, msg ProtoMsg) (ProtoMsg, error) { - return nil, nil + typeUrl := sdk.MsgTypeURL(msg) + switch typeUrl { + case "/cosmos.staking.v1beta1.MsgDelegate": + return &stakingtypes.MsgDelegateResponse{}, nil + case "/cosmos.staking.v1beta1.MsgUndelegate": + return &stakingtypes.MsgUndelegate{}, nil + case "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward": + return &distrtypes.MsgWithdrawDelegatorRewardResponse{}, nil + case "/cosmos.bank.v1beta1.MsgSend": + return &banktypes.MsgSendResponse{}, nil + default: + return nil, fmt.Errorf("unrecognized request type") + } }, func(ctx context.Context, req, resp ProtoMsg) error { _, ok := req.(*banktypes.QueryBalanceRequest) if !ok { @@ -70,14 +99,16 @@ func newMockContext(t *testing.T) (context.Context, store.KVStoreService) { BondDenom: "test", }, }) - return nil + } else { + // NOTE: using gogoproto.Merge will fail for some reason unknown to me, but + // using proto.Merge with gogo messages seems to work fine. + proto.Merge(resp.(gogoproto.Message), &banktypes.QueryBalanceResponse{ + Balance: &(sdk.Coin{ + Denom: "test", + Amount: TestFunds.AmountOf("test"), + }), + }) } - gogoproto.Merge(resp.(gogoproto.Message), &banktypes.QueryBalanceResponse{ - Balance: &sdk.Coin{ - Denom: "test", - Amount: math.NewInt(5), - }, - }) return nil }, @@ -91,5 +122,8 @@ func makeMockDependencies(storeservice store.KVStoreService) accountstd.Dependen SchemaBuilder: sb, AddressCodec: addressCodec{}, LegacyStateCodec: mockStateCodec{}, + Environment: appmodule.Environment{ + HeaderService: headerService{}, + }, } }