test(accounts): add multisig e2e tests (#20310)
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
package multisig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
"cosmossdk.io/core/header"
|
||||
"cosmossdk.io/math"
|
||||
v1 "cosmossdk.io/x/accounts/defaults/multisig/v1"
|
||||
accountsv1 "cosmossdk.io/x/accounts/v1"
|
||||
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func TestE2ETestSuite(t *testing.T) {
|
||||
suite.Run(t, NewE2ETestSuite())
|
||||
}
|
||||
|
||||
// TestSimpleSendProposal creates a multisig account with 1 member, sends a tx, votes and executes it.
|
||||
func (s *E2ETestSuite) TestSimpleSendProposal() {
|
||||
ctx := sdk.NewContext(s.app.CommitMultiStore(), false, s.app.Logger()).WithHeaderInfo(header.Info{
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
randAcc := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
|
||||
addr, err := s.app.AuthKeeper.AddressCodec().BytesToString(randAcc)
|
||||
s.NoError(err)
|
||||
|
||||
initialMembers := map[string]uint64{
|
||||
s.membersAddr[0]: 100,
|
||||
}
|
||||
accountAddr, accAddrStr := s.initAccount(ctx, s.members[0], initialMembers)
|
||||
|
||||
balance := s.app.BankKeeper.GetBalance(ctx, randAcc, "stake")
|
||||
s.Equal(math.NewInt(0), balance.Amount)
|
||||
|
||||
// do a simple bank send
|
||||
msg := &bankv1beta1.MsgSend{
|
||||
FromAddress: accAddrStr,
|
||||
ToAddress: addr,
|
||||
Amount: []*basev1beta1.Coin{
|
||||
{
|
||||
Denom: "stake",
|
||||
Amount: "100",
|
||||
},
|
||||
},
|
||||
}
|
||||
anyMsg, err := codectypes.NewAnyWithValue(msg)
|
||||
s.NoError(err)
|
||||
|
||||
s.createProposal(ctx, accountAddr, s.members[0], anyMsg)
|
||||
|
||||
// now we vote for it
|
||||
voteReq := &v1.MsgVote{
|
||||
ProposalId: 0,
|
||||
Vote: v1.VoteOption_VOTE_OPTION_YES,
|
||||
}
|
||||
|
||||
err = s.executeTx(ctx, voteReq, accountAddr, s.members[0])
|
||||
s.NoError(err)
|
||||
|
||||
// now we execute it
|
||||
err = s.executeProposal(ctx, accountAddr, s.members[0], 0)
|
||||
s.NoError(err)
|
||||
|
||||
foundPropResult := false
|
||||
for _, v := range ctx.EventManager().Events() {
|
||||
if v.Type == "proposal_tally" {
|
||||
foundPropResult = true
|
||||
status, found := v.GetAttribute("status")
|
||||
s.True(found)
|
||||
s.Equal(v1.ProposalStatus_PROPOSAL_STATUS_PASSED.String(), status.Value)
|
||||
|
||||
yesVotes, found := v.GetAttribute("yes_votes")
|
||||
s.True(found)
|
||||
s.Equal("100", yesVotes.Value)
|
||||
|
||||
noVotes, found := v.GetAttribute("no_votes")
|
||||
s.True(found)
|
||||
s.Equal("0", noVotes.Value)
|
||||
|
||||
propID, found := v.GetAttribute("proposal_id")
|
||||
s.True(found)
|
||||
s.Equal("0", propID.Value)
|
||||
|
||||
execErr, found := v.GetAttribute("exec_err")
|
||||
s.True(found)
|
||||
s.Equal("<nil>", execErr.Value)
|
||||
|
||||
rejectErr, found := v.GetAttribute("reject_err")
|
||||
s.True(found)
|
||||
s.Equal("<nil>", rejectErr.Value)
|
||||
}
|
||||
}
|
||||
s.True(foundPropResult)
|
||||
|
||||
balance = s.app.BankKeeper.GetBalance(ctx, randAcc, "stake")
|
||||
s.Equal(int64(100), balance.Amount.Int64())
|
||||
|
||||
// try to execute again, should fail
|
||||
err = s.executeProposal(ctx, accountAddr, s.members[0], 0)
|
||||
s.Error(err)
|
||||
}
|
||||
|
||||
// TestConfigUpdate creates a multisig with 1 member, adds 2 more members and
|
||||
// changes the config to require 2/3 majority (also through a proposal).
|
||||
func (s *E2ETestSuite) TestConfigUpdate() {
|
||||
ctx := sdk.NewContext(s.app.CommitMultiStore(), false, s.app.Logger()).WithHeaderInfo(header.Info{
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
initialMembers := map[string]uint64{
|
||||
s.membersAddr[0]: 100,
|
||||
}
|
||||
accountAddr, accAddrStr := s.initAccount(ctx, s.members[0], initialMembers)
|
||||
|
||||
// Add 2 members and pass the proposal
|
||||
// create proposal
|
||||
updateMsg := &v1.MsgUpdateConfig{
|
||||
UpdateMembers: []*v1.Member{
|
||||
{
|
||||
Address: s.membersAddr[1],
|
||||
Weight: 100,
|
||||
},
|
||||
{
|
||||
Address: s.membersAddr[2],
|
||||
Weight: 100,
|
||||
},
|
||||
},
|
||||
Config: &v1.Config{
|
||||
Threshold: 200, // 3 members with 100 power each, 2/3 majority
|
||||
Quorum: 200,
|
||||
VotingPeriod: 120,
|
||||
Revote: false,
|
||||
EarlyExecution: false,
|
||||
},
|
||||
}
|
||||
|
||||
msgExec := &accountsv1.MsgExecute{
|
||||
Sender: accAddrStr,
|
||||
Target: accAddrStr,
|
||||
Message: codectypes.UnsafePackAny(updateMsg),
|
||||
Funds: []sdk.Coin{},
|
||||
}
|
||||
|
||||
s.createProposal(ctx, accountAddr, s.members[0], codectypes.UnsafePackAny(msgExec))
|
||||
|
||||
// vote
|
||||
voteReq := &v1.MsgVote{
|
||||
ProposalId: 0,
|
||||
Vote: v1.VoteOption_VOTE_OPTION_YES,
|
||||
}
|
||||
|
||||
err := s.executeTx(ctx, voteReq, accountAddr, s.members[0])
|
||||
s.NoError(err)
|
||||
|
||||
err = s.executeProposal(ctx, accountAddr, s.members[0], 0)
|
||||
s.NoError(err)
|
||||
|
||||
// get members
|
||||
res, err := s.queryAcc(ctx, &v1.QueryConfig{}, accountAddr)
|
||||
s.NoError(err)
|
||||
resp := res.(*v1.QueryConfigResponse)
|
||||
s.Len(resp.Members, 3)
|
||||
s.Equal(int64(200), resp.Config.Threshold)
|
||||
|
||||
// Try to remove a member, but it doesn't reach passing threshold
|
||||
// create proposal
|
||||
msgExec = &accountsv1.MsgExecute{
|
||||
Sender: accAddrStr,
|
||||
Target: accAddrStr,
|
||||
Message: codectypes.UnsafePackAny(&v1.MsgUpdateConfig{
|
||||
UpdateMembers: []*v1.Member{
|
||||
{
|
||||
Address: s.membersAddr[1],
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
Config: &v1.Config{
|
||||
Threshold: 200, // 3 members with 100 power each, 2/3 majority
|
||||
Quorum: 200,
|
||||
VotingPeriod: 120,
|
||||
Revote: false,
|
||||
EarlyExecution: false,
|
||||
},
|
||||
}),
|
||||
Funds: []sdk.Coin{},
|
||||
}
|
||||
|
||||
s.createProposal(ctx, accountAddr, s.members[0], codectypes.UnsafePackAny(msgExec))
|
||||
|
||||
// vote
|
||||
voteReq = &v1.MsgVote{
|
||||
ProposalId: 1,
|
||||
Vote: v1.VoteOption_VOTE_OPTION_NO,
|
||||
}
|
||||
|
||||
err = s.executeTx(ctx, voteReq, accountAddr, s.members[0])
|
||||
s.NoError(err)
|
||||
|
||||
// need to wait until voting period is over because we disabled early execution on the last
|
||||
// config update
|
||||
err = s.executeProposal(ctx, accountAddr, s.members[0], 0)
|
||||
s.ErrorContains(err, "voting period has not ended yet, and early execution is not enabled")
|
||||
|
||||
// vote with member 1
|
||||
voteReq = &v1.MsgVote{
|
||||
ProposalId: 1,
|
||||
Vote: v1.VoteOption_VOTE_OPTION_NO,
|
||||
}
|
||||
|
||||
err = s.executeTx(ctx, voteReq, accountAddr, s.members[1])
|
||||
s.NoError(err)
|
||||
|
||||
// need to wait until voting period is over because we disabled early execution on the last
|
||||
// config update
|
||||
err = s.executeProposal(ctx, accountAddr, s.members[0], 1)
|
||||
s.ErrorContains(err, "voting period has not ended yet, and early execution is not enabled")
|
||||
|
||||
headerInfo := ctx.HeaderInfo()
|
||||
headerInfo.Time = headerInfo.Time.Add(time.Second * 121)
|
||||
ctx = ctx.WithHeaderInfo(headerInfo)
|
||||
|
||||
// now it should work, but the proposal will fail
|
||||
err = s.executeProposal(ctx, accountAddr, s.members[0], 1)
|
||||
s.NoError(err)
|
||||
|
||||
foundPropResult := false
|
||||
for _, v := range ctx.EventManager().Events() {
|
||||
if v.Type == "proposal_tally" {
|
||||
propID, found := v.GetAttribute("proposal_id")
|
||||
s.True(found)
|
||||
|
||||
if propID.Value == "1" {
|
||||
foundPropResult = true
|
||||
status, found := v.GetAttribute("status")
|
||||
s.True(found)
|
||||
s.Equal(v1.ProposalStatus_PROPOSAL_STATUS_REJECTED.String(), status.Value)
|
||||
|
||||
// exec_err is nil because the proposal didn't execute
|
||||
execErr, found := v.GetAttribute("exec_err")
|
||||
s.True(found)
|
||||
s.Equal("<nil>", execErr.Value)
|
||||
|
||||
rejectErr, found := v.GetAttribute("reject_err")
|
||||
s.True(found)
|
||||
s.Equal("threshold not reached", rejectErr.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.True(foundPropResult)
|
||||
|
||||
// get members
|
||||
res, err = s.queryAcc(ctx, &v1.QueryConfig{}, accountAddr)
|
||||
s.NoError(err)
|
||||
resp = res.(*v1.QueryConfigResponse)
|
||||
s.Len(resp.Members, 3)
|
||||
s.Equal(int64(200), resp.Config.Threshold)
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package multisig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"google.golang.org/protobuf/runtime/protoiface"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
"cosmossdk.io/simapp"
|
||||
multisigaccount "cosmossdk.io/x/accounts/defaults/multisig"
|
||||
v1 "cosmossdk.io/x/accounts/defaults/multisig/v1"
|
||||
"cosmossdk.io/x/bank/testutil"
|
||||
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type ProtoMsg = protoiface.MessageV1
|
||||
|
||||
type E2ETestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
app *simapp.SimApp
|
||||
members []sdk.AccAddress
|
||||
membersAddr []string
|
||||
}
|
||||
|
||||
func NewE2ETestSuite() *E2ETestSuite {
|
||||
return &E2ETestSuite{}
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) SetupSuite() {
|
||||
s.app = setupApp(s.T())
|
||||
|
||||
s.members = []sdk.AccAddress{}
|
||||
for i := 0; i < 10; i++ {
|
||||
addr := secp256k1.GenPrivKey().PubKey().Address()
|
||||
addrStr, err := s.app.AuthKeeper.AddressCodec().BytesToString(addr)
|
||||
require.NoError(s.T(), err)
|
||||
s.membersAddr = append(s.membersAddr, addrStr)
|
||||
s.members = append(s.members, sdk.AccAddress(addr))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TearDownSuite() {}
|
||||
|
||||
func setupApp(t *testing.T) *simapp.SimApp {
|
||||
t.Helper()
|
||||
app := simapp.Setup(t, false)
|
||||
return app
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) executeTx(ctx context.Context, msg sdk.Msg, accAddr, sender []byte) error {
|
||||
_, err := s.app.AccountsKeeper.Execute(ctx, accAddr, sender, msg, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) queryAcc(ctx context.Context, req sdk.Msg, accAddr []byte) (ProtoMsg, error) {
|
||||
resp, err := s.app.AccountsKeeper.Query(ctx, accAddr, req)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) fundAccount(ctx context.Context, addr sdk.AccAddress, amt sdk.Coins) {
|
||||
require.NoError(s.T(), testutil.FundAccount(ctx, s.app.BankKeeper, addr, amt))
|
||||
}
|
||||
|
||||
// initAccount initializes a multisig account with the given members and powers
|
||||
// and returns the account address
|
||||
func (s *E2ETestSuite) initAccount(ctx context.Context, sender []byte, membersPowers map[string]uint64) ([]byte, string) {
|
||||
s.fundAccount(ctx, sender, sdk.Coins{sdk.NewCoin("stake", math.NewInt(1000000))})
|
||||
|
||||
members := []*v1.Member{}
|
||||
for addrStr, power := range membersPowers {
|
||||
members = append(members, &v1.Member{Address: addrStr, Weight: power})
|
||||
}
|
||||
|
||||
_, accountAddr, err := s.app.AccountsKeeper.Init(ctx, multisigaccount.MULTISIG_ACCOUNT, sender,
|
||||
&v1.MsgInit{
|
||||
Members: members,
|
||||
Config: &v1.Config{
|
||||
Threshold: 100,
|
||||
Quorum: 100,
|
||||
VotingPeriod: 120,
|
||||
Revote: false,
|
||||
EarlyExecution: true,
|
||||
},
|
||||
}, sdk.Coins{sdk.NewCoin("stake", math.NewInt(1000))})
|
||||
s.NoError(err)
|
||||
|
||||
accountAddrStr, err := s.app.AuthKeeper.AddressCodec().BytesToString(accountAddr)
|
||||
s.NoError(err)
|
||||
|
||||
return accountAddr, accountAddrStr
|
||||
}
|
||||
|
||||
// createProposal
|
||||
func (s *E2ETestSuite) createProposal(ctx context.Context, accAddr, sender []byte, msgs ...*codectypes.Any) {
|
||||
propReq := &v1.MsgCreateProposal{
|
||||
Proposal: &v1.Proposal{
|
||||
Title: "test",
|
||||
Summary: "test",
|
||||
Messages: msgs,
|
||||
},
|
||||
}
|
||||
err := s.executeTx(ctx, propReq, accAddr, sender)
|
||||
s.NoError(err)
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) executeProposal(ctx context.Context, accAddr, sender []byte, proposalID uint64) error {
|
||||
execReq := &v1.MsgExecuteProposal{
|
||||
ProposalId: proposalID,
|
||||
}
|
||||
return s.executeTx(ctx, execReq, accAddr, sender)
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
var MULTISIG_ACCOUNT = "multisig-account"
|
||||
|
||||
var (
|
||||
MembersPrefix = collections.NewPrefix(0)
|
||||
SequencePrefix = collections.NewPrefix(1)
|
||||
@@ -43,19 +45,17 @@ type Account struct {
|
||||
}
|
||||
|
||||
// NewAccount returns a new multisig account creator function.
|
||||
func NewAccount(name string) accountstd.AccountCreatorFunc {
|
||||
return func(deps accountstd.Dependencies) (string, accountstd.Interface, error) {
|
||||
return name, &Account{
|
||||
Members: collections.NewMap(deps.SchemaBuilder, MembersPrefix, "members", collections.BytesKey, collections.Uint64Value),
|
||||
Sequence: collections.NewSequence(deps.SchemaBuilder, SequencePrefix, "sequence"),
|
||||
Config: collections.NewItem(deps.SchemaBuilder, ConfigPrefix, "config", codec.CollValue[v1.Config](deps.LegacyStateCodec)),
|
||||
Proposals: collections.NewMap(deps.SchemaBuilder, ProposalsPrefix, "proposals", collections.Uint64Key, codec.CollValue[v1.Proposal](deps.LegacyStateCodec)),
|
||||
Votes: collections.NewMap(deps.SchemaBuilder, VotesPrefix, "votes", collections.PairKeyCodec(collections.Uint64Key, collections.BytesKey), collections.Int32Value),
|
||||
addrCodec: deps.AddressCodec,
|
||||
headerService: deps.Environment.HeaderService,
|
||||
eventService: deps.Environment.EventService,
|
||||
}, nil
|
||||
}
|
||||
func NewAccount(deps accountstd.Dependencies) (*Account, error) {
|
||||
return &Account{
|
||||
Members: collections.NewMap(deps.SchemaBuilder, MembersPrefix, "members", collections.BytesKey, collections.Uint64Value),
|
||||
Sequence: collections.NewSequence(deps.SchemaBuilder, SequencePrefix, "sequence"),
|
||||
Config: collections.NewItem(deps.SchemaBuilder, ConfigPrefix, "config", codec.CollValue[v1.Config](deps.LegacyStateCodec)),
|
||||
Proposals: collections.NewMap(deps.SchemaBuilder, ProposalsPrefix, "proposals", collections.Uint64Key, codec.CollValue[v1.Proposal](deps.LegacyStateCodec)),
|
||||
Votes: collections.NewMap(deps.SchemaBuilder, VotesPrefix, "votes", collections.PairKeyCodec(collections.Uint64Key, collections.BytesKey), collections.Int32Value),
|
||||
addrCodec: deps.AddressCodec,
|
||||
headerService: deps.Environment.HeaderService,
|
||||
eventService: deps.Environment.EventService,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Init initializes the multisig account with the given configuration and members.
|
||||
|
||||
@@ -11,17 +11,17 @@ import (
|
||||
"cosmossdk.io/core/store"
|
||||
"cosmossdk.io/x/accounts/accountstd"
|
||||
v1 "cosmossdk.io/x/accounts/defaults/multisig/v1"
|
||||
accountsv1 "cosmossdk.io/x/accounts/v1"
|
||||
)
|
||||
|
||||
func setup(t *testing.T, ctx context.Context, ss store.KVStoreService, timefn func() time.Time) *Account {
|
||||
t.Helper()
|
||||
deps := makeMockDependencies(ss, timefn)
|
||||
|
||||
multisig := NewAccount("multisig")
|
||||
_, acc, err := multisig(deps)
|
||||
acc, err := NewAccount(deps)
|
||||
require.NoError(t, err)
|
||||
|
||||
return acc.(*Account)
|
||||
return acc
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
@@ -321,6 +321,8 @@ func TestUpdateConfig(t *testing.T) {
|
||||
_, 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)
|
||||
@@ -342,6 +344,51 @@ func TestUpdateConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
@@ -491,12 +538,20 @@ func TestProposalPassing(t *testing.T) {
|
||||
}
|
||||
|
||||
var acc *Account
|
||||
ctx, ss := accountstd.NewMockContext(
|
||||
var ctx context.Context
|
||||
var ss store.KVStoreService
|
||||
ctx, ss = accountstd.NewMockContext(
|
||||
0, []byte("multisig_acc"), []byte("addr1"), TestFunds, func(ctx context.Context, sender []byte, msg, msgResp ProtoMsg) error {
|
||||
return nil
|
||||
}, func(ctx context.Context, sender []byte, msg ProtoMsg) (ProtoMsg, error) {
|
||||
if _, ok := msg.(*v1.MsgUpdateConfig); ok {
|
||||
return acc.UpdateConfig(ctx, msg.(*v1.MsgUpdateConfig))
|
||||
}, func(ictx context.Context, sender []byte, msg ProtoMsg) (ProtoMsg, 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, resp ProtoMsg) error {
|
||||
@@ -523,12 +578,21 @@ func TestProposalPassing(t *testing.T) {
|
||||
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{anymsg},
|
||||
Messages: []*types.Any{execMsgAny},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -572,9 +636,25 @@ func TestProposalPassing(t *testing.T) {
|
||||
// check if addr1's weight changed
|
||||
cfg, err := acc.QueryConfig(ctx, &v1.QueryConfig{})
|
||||
require.NoError(t, err)
|
||||
for _, v := range cfg.Members {
|
||||
if v.Address == "addr1" {
|
||||
require.Equal(t, uint64(500), v.Weight)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,17 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"cosmossdk.io/x/accounts/accountstd"
|
||||
v1 "cosmossdk.io/x/accounts/defaults/multisig/v1"
|
||||
)
|
||||
|
||||
// UpdateConfig updates the configuration of the multisig account.
|
||||
func (a Account) UpdateConfig(ctx context.Context, msg *v1.MsgUpdateConfig) (*v1.MsgUpdateConfigResponse, error) {
|
||||
// this function can only be executed by the account itself
|
||||
if !accountstd.SenderIsSelf(ctx) {
|
||||
return nil, errors.New("only the account itself can update the config (through a proposal)")
|
||||
}
|
||||
|
||||
// set members
|
||||
for i := range msg.UpdateMembers {
|
||||
addrBz, err := a.addrCodec.StringToBytes(msg.UpdateMembers[i].Address)
|
||||
|
||||
@@ -163,7 +163,7 @@ func (a addressCodec) BytesToString(bz []byte) (string, error) { return string
|
||||
func newMockContext(t *testing.T) (context.Context, store.KVStoreService) {
|
||||
t.Helper()
|
||||
return accountstd.NewMockContext(
|
||||
0, []byte("multisig_account"), []byte("sender"), TestFunds, func(ctx context.Context, sender []byte, msg, msgResp ProtoMsg) error {
|
||||
0, []byte("mock_multisig_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
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"cosmossdk.io/x/accounts/accountstd"
|
||||
baseaccount "cosmossdk.io/x/accounts/defaults/base"
|
||||
"cosmossdk.io/x/accounts/defaults/lockup"
|
||||
"cosmossdk.io/x/accounts/defaults/multisig"
|
||||
"cosmossdk.io/x/tx/signing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
@@ -70,6 +71,7 @@ func ProvideModule(in ModuleInputs) ModuleOutputs {
|
||||
accountstd.AddAccount(lockup.PERIODIC_LOCKING_ACCOUNT, lockup.NewPeriodicLockingAccount),
|
||||
accountstd.AddAccount(lockup.DELAYED_LOCKING_ACCOUNT, lockup.NewDelayedLockingAccount),
|
||||
accountstd.AddAccount(lockup.PERMANENT_LOCKING_ACCOUNT, lockup.NewPermanentLockingAccount),
|
||||
accountstd.AddAccount(multisig.MULTISIG_ACCOUNT, multisig.NewAccount),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
Reference in New Issue
Block a user