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

test(mstaking): implement StakeAuthorization tests #340

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 271 additions & 1 deletion x/mstaking/types/authz_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,273 @@
package types_test

// TODO implement test
import (
"context"
"testing"

"github.com/stretchr/testify/require"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/authz"

"github.com/initia-labs/initia/x/mstaking/types"
)

func TestNewStakeAuthorization(t *testing.T) {
tests := []struct {
name string
allowedValidators []string
deniedValidators []string
authzType types.AuthorizationType
amount sdk.Coins
expectError bool
}{
{
name: "valid authorization with allow list",
allowedValidators: []string{"val1", "val2"},
deniedValidators: nil,
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
expectError: false,
},
{
name: "valid authorization with deny list",
allowedValidators: nil,
deniedValidators: []string{"val1", "val2"},
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE,
amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
expectError: false,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
authorization, err := types.NewStakeAuthorization(
tc.allowedValidators,
tc.deniedValidators,
tc.authzType,
tc.amount,
)

if tc.expectError {
require.Error(t, err)
return
}

require.NoError(t, err)
require.NotNil(t, authorization)

if tc.allowedValidators != nil {
require.NotNil(t, authorization.GetAllowList())
require.Equal(t, tc.allowedValidators, authorization.GetAllowList().Address)
}

if tc.deniedValidators != nil {
require.NotNil(t, authorization.GetDenyList())
require.Equal(t, tc.deniedValidators, authorization.GetDenyList().Address)
}

require.Equal(t, tc.authzType, authorization.AuthorizationType)
if tc.amount != nil {
require.Equal(t, tc.amount, authorization.MaxTokens)
}
})
}
}
Comment on lines +16 to +76
Copy link

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 error scenarios.

The test suite only covers positive cases. Consider adding test cases for:

  • Mutually exclusive allow/deny lists
  • Invalid validator addresses
  • Empty validator lists
  • Invalid authorization types

Example test case:

 tests := []struct {
     // ... existing fields ...
 }{
     // ... existing test cases ...
+    {
+        name:              "invalid - both allow and deny lists",
+        allowedValidators: []string{"val1"},
+        deniedValidators:  []string{"val2"},
+        authzType:         types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
+        amount:           sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
+        expectError:      true,
+    },
+    {
+        name:              "invalid - empty validator list",
+        allowedValidators: []string{},
+        deniedValidators:  nil,
+        authzType:         types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
+        amount:           sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
+        expectError:      true,
+    },
 }
📝 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
func TestNewStakeAuthorization(t *testing.T) {
tests := []struct {
name string
allowedValidators []string
deniedValidators []string
authzType types.AuthorizationType
amount sdk.Coins
expectError bool
}{
{
name: "valid authorization with allow list",
allowedValidators: []string{"val1", "val2"},
deniedValidators: nil,
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
expectError: false,
},
{
name: "valid authorization with deny list",
allowedValidators: nil,
deniedValidators: []string{"val1", "val2"},
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE,
amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
expectError: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
authorization, err := types.NewStakeAuthorization(
tc.allowedValidators,
tc.deniedValidators,
tc.authzType,
tc.amount,
)
if tc.expectError {
require.Error(t, err)
return
}
require.NoError(t, err)
require.NotNil(t, authorization)
if tc.allowedValidators != nil {
require.NotNil(t, authorization.GetAllowList())
require.Equal(t, tc.allowedValidators, authorization.GetAllowList().Address)
}
if tc.deniedValidators != nil {
require.NotNil(t, authorization.GetDenyList())
require.Equal(t, tc.deniedValidators, authorization.GetDenyList().Address)
}
require.Equal(t, tc.authzType, authorization.AuthorizationType)
if tc.amount != nil {
require.Equal(t, tc.amount, authorization.MaxTokens)
}
})
}
}
func TestNewStakeAuthorization(t *testing.T) {
tests := []struct {
name string
allowedValidators []string
deniedValidators []string
authzType types.AuthorizationType
amount sdk.Coins
expectError bool
}{
{
name: "valid authorization with allow list",
allowedValidators: []string{"val1", "val2"},
deniedValidators: nil,
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
expectError: false,
},
{
name: "valid authorization with deny list",
allowedValidators: nil,
deniedValidators: []string{"val1", "val2"},
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE,
amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
expectError: false,
},
{
name: "invalid - both allow and deny lists",
allowedValidators: []string{"val1"},
deniedValidators: []string{"val2"},
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
expectError: true,
},
{
name: "invalid - empty validator list",
allowedValidators: []string{},
deniedValidators: nil,
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
expectError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
authorization, err := types.NewStakeAuthorization(
tc.allowedValidators,
tc.deniedValidators,
tc.authzType,
tc.amount,
)
if tc.expectError {
require.Error(t, err)
return
}
require.NoError(t, err)
require.NotNil(t, authorization)
if tc.allowedValidators != nil {
require.NotNil(t, authorization.GetAllowList())
require.Equal(t, tc.allowedValidators, authorization.GetAllowList().Address)
}
if tc.deniedValidators != nil {
require.NotNil(t, authorization.GetDenyList())
require.Equal(t, tc.deniedValidators, authorization.GetDenyList().Address)
}
require.Equal(t, tc.authzType, authorization.AuthorizationType)
if tc.amount != nil {
require.Equal(t, tc.amount, authorization.MaxTokens)
}
})
}
}


func TestStakeAuthorization_ValidateBasic(t *testing.T) {
tests := []struct {
name string
auth types.StakeAuthorization
expectError bool
}{
{
name: "valid authorization",
auth: types.StakeAuthorization{
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
expectError: false,
},
{
name: "invalid - negative coins",
auth: types.StakeAuthorization{
MaxTokens: sdk.Coins{sdk.Coin{Denom: "stake", Amount: math.NewInt(-1000)}},
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
expectError: true,
},
{
name: "invalid - unspecified authorization type",
auth: types.StakeAuthorization{
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_UNSPECIFIED,
},
expectError: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := tc.auth.ValidateBasic()
if tc.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}

func TestStakeAuthorization_Accept(t *testing.T) {
tests := []struct {
name string
auth types.StakeAuthorization
msg sdk.Msg
expectError bool
}{
{
name: "valid delegate with allow list",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_AllowList{
AllowList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val1",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: false,
},
{
name: "invalid - validator not in allow list",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_AllowList{
AllowList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val2",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: true,
},
{
name: "invalid - validator in deny list",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_DenyList{
DenyList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val1",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: true,
},
{
name: "invalid - exceeds max tokens",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_AllowList{
AllowList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(400))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val1",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
resp, err := tc.auth.Accept(context.Background(), tc.msg)
if tc.expectError {
require.Error(t, err)
return
}

require.NoError(t, err)
require.True(t, resp.Accept)

// Check if authorization should be deleted (when max tokens are used up)
if tc.auth.MaxTokens != nil {
msgCoins := tc.msg.(*types.MsgDelegate).Amount
if tc.auth.MaxTokens.Equal(msgCoins) {
require.True(t, resp.Delete)
}
}
})
}
}
Comment on lines +122 to +223
Copy link

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 message validation.

Add test cases for:

  • Nil message
  • Wrong message type
  • Invalid delegator address
  • Invalid amount denomination

Example test cases:

 tests := []struct {
     // ... existing fields ...
 }{
     // ... existing test cases ...
+    {
+        name: "invalid - nil message",
+        auth: types.StakeAuthorization{
+            MaxTokens:         sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
+            AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
+        },
+        msg:         nil,
+        expectError: true,
+    },
+    {
+        name: "invalid - wrong message type",
+        auth: types.StakeAuthorization{
+            MaxTokens:         sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
+            AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
+        },
+        msg:         &types.MsgUndelegate{},
+        expectError: true,
+    },
 }
📝 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
func TestStakeAuthorization_Accept(t *testing.T) {
tests := []struct {
name string
auth types.StakeAuthorization
msg sdk.Msg
expectError bool
}{
{
name: "valid delegate with allow list",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_AllowList{
AllowList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val1",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: false,
},
{
name: "invalid - validator not in allow list",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_AllowList{
AllowList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val2",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: true,
},
{
name: "invalid - validator in deny list",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_DenyList{
DenyList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val1",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: true,
},
{
name: "invalid - exceeds max tokens",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_AllowList{
AllowList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(400))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val1",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
resp, err := tc.auth.Accept(context.Background(), tc.msg)
if tc.expectError {
require.Error(t, err)
return
}
require.NoError(t, err)
require.True(t, resp.Accept)
// Check if authorization should be deleted (when max tokens are used up)
if tc.auth.MaxTokens != nil {
msgCoins := tc.msg.(*types.MsgDelegate).Amount
if tc.auth.MaxTokens.Equal(msgCoins) {
require.True(t, resp.Delete)
}
}
})
}
}
func TestStakeAuthorization_Accept(t *testing.T) {
tests := []struct {
name string
auth types.StakeAuthorization
msg sdk.Msg
expectError bool
}{
{
name: "valid delegate with allow list",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_AllowList{
AllowList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val1",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: false,
},
{
name: "invalid - validator not in allow list",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_AllowList{
AllowList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val2",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: true,
},
{
name: "invalid - validator in deny list",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_DenyList{
DenyList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val1",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: true,
},
{
name: "invalid - exceeds max tokens",
auth: types.StakeAuthorization{
Validators: &types.StakeAuthorization_AllowList{
AllowList: &types.StakeAuthorization_Validators{
Address: []string{"val1"},
},
},
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(400))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgDelegate{
DelegatorAddress: "delegator",
ValidatorAddress: "val1",
Amount: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(500))),
},
expectError: true,
},
{
name: "invalid - nil message",
auth: types.StakeAuthorization{
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: nil,
expectError: true,
},
{
name: "invalid - wrong message type",
auth: types.StakeAuthorization{
MaxTokens: sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(1000))),
AuthorizationType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
},
msg: &types.MsgUndelegate{},
expectError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
resp, err := tc.auth.Accept(context.Background(), tc.msg)
if tc.expectError {
require.Error(t, err)
return
}
require.NoError(t, err)
require.True(t, resp.Accept)
// Check if authorization should be deleted (when max tokens are used up)
if tc.auth.MaxTokens != nil {
msgCoins := tc.msg.(*types.MsgDelegate).Amount
if tc.auth.MaxTokens.Equal(msgCoins) {
require.True(t, resp.Delete)
}
}
})
}
}


func TestStakeAuthorization_MsgTypeURL(t *testing.T) {
tests := []struct {
name string
authzType types.AuthorizationType
expectError bool
}{
{
name: "delegate authorization",
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE,
expectError: false,
},
{
name: "undelegate authorization",
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE,
expectError: false,
},
{
name: "redelegate authorization",
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE,
expectError: false,
},
{
name: "cancel unbonding delegation authorization",
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION,
expectError: false,
},
{
name: "unspecified authorization type",
authzType: types.AuthorizationType_AUTHORIZATION_TYPE_UNSPECIFIED,
expectError: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
auth := types.StakeAuthorization{
AuthorizationType: tc.authzType,
}

if tc.expectError {
require.Panics(t, func() { auth.MsgTypeURL() })
return
}

url := auth.MsgTypeURL()
require.NotEmpty(t, url)
})
}
}