713 lines
14 KiB
Go
713 lines
14 KiB
Go
package multisig
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
|
|
types "github.com/cosmos/gogoproto/types/any"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"cosmossdk.io/core/store"
|
|
"cosmossdk.io/core/transaction"
|
|
"cosmossdk.io/x/accounts/accountstd"
|
|
v1 "cosmossdk.io/x/accounts/defaults/multisig/v1"
|
|
accountsv1 "cosmossdk.io/x/accounts/v1"
|
|
)
|
|
|
|
func setup(t *testing.T, _ context.Context, ss store.KVStoreService, timefn func() time.Time) *Account {
|
|
t.Helper()
|
|
deps := makeMockDependencies(ss, timefn)
|
|
|
|
acc, err := NewAccount(deps)
|
|
require.NoError(t, err)
|
|
|
|
return acc
|
|
}
|
|
|
|
func TestInit(t *testing.T) {
|
|
ctx, ss := newMockContext(t)
|
|
acc := setup(t, ctx, ss, nil)
|
|
|
|
testcases := []struct {
|
|
name string
|
|
msg *v1.MsgInit
|
|
expErr string
|
|
}{
|
|
{
|
|
"success",
|
|
&v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 666,
|
|
Quorum: 400,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 500,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1000,
|
|
},
|
|
},
|
|
},
|
|
"",
|
|
},
|
|
{
|
|
"no members",
|
|
&v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 666,
|
|
Quorum: 400,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{},
|
|
},
|
|
"members must be specified",
|
|
},
|
|
{
|
|
"no config",
|
|
&v1.MsgInit{
|
|
Config: nil,
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 500,
|
|
},
|
|
},
|
|
},
|
|
"config must be specified",
|
|
},
|
|
{
|
|
"member weight zero",
|
|
&v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 666,
|
|
Quorum: 400,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 0,
|
|
},
|
|
},
|
|
},
|
|
"member weight must be greater than zero",
|
|
},
|
|
{
|
|
"threshold is zero",
|
|
&v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 0,
|
|
Quorum: 400,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 500,
|
|
},
|
|
},
|
|
},
|
|
"threshold, quorum and voting period must be greater than zero",
|
|
},
|
|
{
|
|
"threshold greater than total weight",
|
|
&v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 2000,
|
|
Quorum: 400,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 500,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1000,
|
|
},
|
|
},
|
|
},
|
|
"threshold must be less than or equal to the total weight",
|
|
},
|
|
{
|
|
"quorum greater than total weight",
|
|
&v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 666,
|
|
Quorum: 2000,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 500,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1000,
|
|
},
|
|
},
|
|
},
|
|
"quorum must be less than or equal to the total weight",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := acc.Init(ctx, tc.msg)
|
|
if tc.expErr != "" {
|
|
require.EqualError(t, err, tc.expErr)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateConfig(t *testing.T) {
|
|
// all test cases start from the same initial state
|
|
startAcc := &v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 2640,
|
|
Quorum: 2000,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr3",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr4",
|
|
Weight: 1000,
|
|
},
|
|
},
|
|
}
|
|
|
|
testcases := []struct {
|
|
name string
|
|
msg *v1.MsgUpdateConfig
|
|
expErr string
|
|
expCfg *v1.Config
|
|
expMembers []*v1.Member
|
|
}{
|
|
{
|
|
"change members",
|
|
&v1.MsgUpdateConfig{
|
|
UpdateMembers: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 500,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 700,
|
|
},
|
|
},
|
|
Config: &v1.Config{
|
|
Threshold: 666,
|
|
Quorum: 400,
|
|
VotingPeriod: 60,
|
|
},
|
|
},
|
|
"",
|
|
&v1.Config{
|
|
Threshold: 666,
|
|
Quorum: 400,
|
|
VotingPeriod: 60,
|
|
},
|
|
[]*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 500,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 700,
|
|
},
|
|
{
|
|
Address: "addr3",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr4",
|
|
Weight: 1000,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"remove member",
|
|
&v1.MsgUpdateConfig{
|
|
UpdateMembers: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 0,
|
|
},
|
|
},
|
|
Config: nil,
|
|
},
|
|
"",
|
|
nil,
|
|
[]*v1.Member{
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr3",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr4",
|
|
Weight: 1000,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"add member",
|
|
&v1.MsgUpdateConfig{
|
|
UpdateMembers: []*v1.Member{
|
|
{
|
|
Address: "addr5",
|
|
Weight: 200,
|
|
},
|
|
},
|
|
Config: nil,
|
|
},
|
|
"",
|
|
nil,
|
|
[]*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr3",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr4",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr5",
|
|
Weight: 200,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"change members, invalid weights",
|
|
&v1.MsgUpdateConfig{
|
|
UpdateMembers: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: math.MaxUint64,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1,
|
|
},
|
|
},
|
|
Config: &v1.Config{
|
|
Threshold: 666,
|
|
Quorum: 400,
|
|
VotingPeriod: 60,
|
|
},
|
|
},
|
|
"overflow",
|
|
nil,
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx, ss := newMockContext(t)
|
|
acc := setup(t, ctx, ss, nil)
|
|
_, err := acc.Init(ctx, startAcc)
|
|
require.NoError(t, err)
|
|
|
|
ctx = accountstd.SetSender(ctx, []byte("mock_multisig_account"))
|
|
|
|
_, err = acc.UpdateConfig(ctx, tc.msg)
|
|
if tc.expErr != "" {
|
|
require.EqualError(t, err, tc.expErr)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
cfg, err := acc.QueryConfig(ctx, &v1.QueryConfig{})
|
|
require.NoError(t, err)
|
|
|
|
// if we are not changing the config, we expect the same config as init
|
|
if tc.expCfg == nil {
|
|
require.Equal(t, startAcc.Config, cfg.Config)
|
|
} else {
|
|
require.Equal(t, tc.expCfg, cfg.Config)
|
|
}
|
|
require.Equal(t, tc.expMembers, cfg.Members)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProposal_WrongSender(t *testing.T) {
|
|
startAcc := &v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 2640,
|
|
Quorum: 2000,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr3",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr4",
|
|
Weight: 1000,
|
|
},
|
|
},
|
|
}
|
|
|
|
ctx, ss := newMockContext(t)
|
|
acc := setup(t, ctx, ss, nil)
|
|
_, err := acc.Init(ctx, startAcc)
|
|
require.NoError(t, err)
|
|
|
|
// change the sender to be something else to trigger an error
|
|
ctx = accountstd.SetSender(ctx, []byte("wrong_sender_here"))
|
|
|
|
newCfg := startAcc.Config
|
|
newCfg.VotingPeriod = 120
|
|
updateCfg := &v1.MsgUpdateConfig{
|
|
Config: newCfg,
|
|
}
|
|
|
|
_, err = acc.UpdateConfig(ctx, updateCfg)
|
|
require.ErrorContains(t, err, "only the account itself can update the config (through a proposal)")
|
|
}
|
|
|
|
func TestProposal_NotPassing(t *testing.T) {
|
|
// all test cases start from the same initial state
|
|
startAcc := &v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 2640,
|
|
Quorum: 2000,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr3",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr4",
|
|
Weight: 1000,
|
|
},
|
|
},
|
|
}
|
|
|
|
ctx, ss := accountstd.NewMockContext(
|
|
0, []byte("multisig_acc"), []byte("addr1"), TestFunds, func(ctx context.Context, sender []byte, msg transaction.Msg) (transaction.Msg, error) {
|
|
if _, ok := msg.(*v1.MsgUpdateConfig); ok {
|
|
return &v1.MsgUpdateConfigResponse{}, nil
|
|
}
|
|
return nil, nil
|
|
}, func(ctx context.Context, req transaction.Msg) (transaction.Msg, error) {
|
|
return nil, nil
|
|
},
|
|
)
|
|
|
|
currentTime := time.Now()
|
|
|
|
acc := setup(t, ctx, ss, func() time.Time {
|
|
return currentTime
|
|
})
|
|
_, err := acc.Init(ctx, startAcc)
|
|
require.NoError(t, err)
|
|
|
|
msg := &v1.MsgUpdateConfig{
|
|
UpdateMembers: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 500,
|
|
},
|
|
},
|
|
}
|
|
anymsg, err := accountstd.PackAny(msg)
|
|
require.NoError(t, err)
|
|
|
|
// create a proposal
|
|
createRes, err := acc.CreateProposal(ctx, &v1.MsgCreateProposal{
|
|
Proposal: &v1.Proposal{
|
|
Title: "test",
|
|
Summary: "test",
|
|
Messages: []*types.Any{anymsg},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
propId := createRes.ProposalId
|
|
|
|
_, err = acc.Vote(ctx, &v1.MsgVote{
|
|
ProposalId: propId,
|
|
Vote: v1.VoteOption_VOTE_OPTION_YES,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = acc.Vote(ctx, &v1.MsgVote{
|
|
ProposalId: propId,
|
|
Vote: v1.VoteOption_VOTE_OPTION_YES,
|
|
})
|
|
require.ErrorContains(t, err, "voter has already voted, can't change its vote per config")
|
|
|
|
ctx = accountstd.SetSender(ctx, []byte("addr2"))
|
|
_, err = acc.Vote(ctx, &v1.MsgVote{
|
|
ProposalId: propId,
|
|
Vote: v1.VoteOption_VOTE_OPTION_YES,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// try to execute the proposal
|
|
_, err = acc.ExecuteProposal(ctx, &v1.MsgExecuteProposal{
|
|
ProposalId: propId,
|
|
})
|
|
require.ErrorContains(t, err, "voting period has not ended yet")
|
|
|
|
// fast forward time
|
|
currentTime = currentTime.Add(61 * time.Second)
|
|
_, err = acc.ExecuteProposal(ctx, &v1.MsgExecuteProposal{
|
|
ProposalId: propId,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// check proposal status
|
|
prop, err := acc.QueryProposal(ctx, &v1.QueryProposal{
|
|
ProposalId: propId,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, v1.ProposalStatus_PROPOSAL_STATUS_REJECTED, prop.Proposal.Status)
|
|
|
|
// vote with addr3
|
|
ctx = accountstd.SetSender(ctx, []byte("addr3"))
|
|
_, err = acc.Vote(ctx, &v1.MsgVote{
|
|
ProposalId: propId,
|
|
Vote: v1.VoteOption_VOTE_OPTION_YES,
|
|
})
|
|
require.ErrorContains(t, err, "voting period has ended")
|
|
}
|
|
|
|
func TestProposalPassing(t *testing.T) {
|
|
// all test cases start from the same initial state
|
|
startAcc := &v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 2640,
|
|
Quorum: 2000,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr3",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr4",
|
|
Weight: 1000,
|
|
},
|
|
},
|
|
}
|
|
|
|
var acc *Account
|
|
var ctx context.Context
|
|
var ss store.KVStoreService
|
|
ctx, ss = accountstd.NewMockContext(
|
|
0, []byte("multisig_acc"), []byte("addr1"), TestFunds,
|
|
func(ictx context.Context, sender []byte, msg transaction.Msg) (transaction.Msg, error) {
|
|
if execmsg, ok := msg.(*accountsv1.MsgExecute); ok {
|
|
updateCfg, err := accountstd.UnpackAny[v1.MsgUpdateConfig](execmsg.GetMessage())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctx = accountstd.SetSender(ctx, []byte("multisig_acc"))
|
|
return acc.UpdateConfig(ctx, updateCfg)
|
|
}
|
|
return nil, nil
|
|
}, func(ctx context.Context, req transaction.Msg) (transaction.Msg, error) {
|
|
return nil, nil
|
|
},
|
|
)
|
|
|
|
currentTime := time.Now()
|
|
|
|
acc = setup(t, ctx, ss, func() time.Time {
|
|
return currentTime
|
|
})
|
|
_, err := acc.Init(ctx, startAcc)
|
|
require.NoError(t, err)
|
|
|
|
msg := &v1.MsgUpdateConfig{
|
|
UpdateMembers: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 500,
|
|
},
|
|
},
|
|
}
|
|
anymsg, err := accountstd.PackAny(msg)
|
|
require.NoError(t, err)
|
|
|
|
execMsg := &accountsv1.MsgExecute{
|
|
Sender: "multisig_acc",
|
|
Target: "multisig_acc",
|
|
Message: anymsg,
|
|
Funds: nil,
|
|
}
|
|
execMsgAny, err := accountstd.PackAny(execMsg)
|
|
require.NoError(t, err)
|
|
|
|
// create a proposal
|
|
createRes, err := acc.CreateProposal(ctx, &v1.MsgCreateProposal{
|
|
Proposal: &v1.Proposal{
|
|
Title: "test",
|
|
Summary: "test",
|
|
Messages: []*types.Any{execMsgAny},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
propId := createRes.ProposalId
|
|
|
|
_, err = acc.Vote(ctx, &v1.MsgVote{
|
|
ProposalId: propId,
|
|
Vote: v1.VoteOption_VOTE_OPTION_YES,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = acc.Vote(ctx, &v1.MsgVote{
|
|
ProposalId: propId,
|
|
Vote: v1.VoteOption_VOTE_OPTION_YES,
|
|
})
|
|
require.ErrorContains(t, err, "voter has already voted, can't change its vote per config")
|
|
|
|
ctx = accountstd.SetSender(ctx, []byte("addr2"))
|
|
_, err = acc.Vote(ctx, &v1.MsgVote{
|
|
ProposalId: propId,
|
|
Vote: v1.VoteOption_VOTE_OPTION_YES,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// vote with addr3
|
|
ctx = accountstd.SetSender(ctx, []byte("addr3"))
|
|
_, err = acc.Vote(ctx, &v1.MsgVote{
|
|
ProposalId: propId,
|
|
Vote: v1.VoteOption_VOTE_OPTION_YES,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// fast forward time
|
|
currentTime = currentTime.Add(61 * time.Second)
|
|
_, err = acc.ExecuteProposal(ctx, &v1.MsgExecuteProposal{
|
|
ProposalId: propId,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// check if addr1's weight changed
|
|
cfg, err := acc.QueryConfig(ctx, &v1.QueryConfig{})
|
|
require.NoError(t, err)
|
|
|
|
expectedMembers := []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: 500,
|
|
},
|
|
{
|
|
Address: "addr2",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr3",
|
|
Weight: 1000,
|
|
},
|
|
{
|
|
Address: "addr4",
|
|
Weight: 1000,
|
|
},
|
|
}
|
|
|
|
require.Equal(t, expectedMembers, cfg.Members)
|
|
}
|
|
|
|
func TestWeightOverflow(t *testing.T) {
|
|
ctx, ss := newMockContext(t)
|
|
acc := setup(t, ctx, ss, nil)
|
|
|
|
startAcc := &v1.MsgInit{
|
|
Config: &v1.Config{
|
|
Threshold: 2640,
|
|
Quorum: 2000,
|
|
VotingPeriod: 60,
|
|
},
|
|
Members: []*v1.Member{
|
|
{
|
|
Address: "addr1",
|
|
Weight: math.MaxUint64,
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := acc.Init(ctx, startAcc)
|
|
require.NoError(t, err)
|
|
|
|
// add another member with weight 1 to trigger an overflow
|
|
startAcc.Members = append(startAcc.Members, &v1.Member{
|
|
Address: "addr2",
|
|
Weight: 1,
|
|
})
|
|
_, err = acc.Init(ctx, startAcc)
|
|
require.ErrorContains(t, err, "overflow")
|
|
}
|