Skip to content

Commit

Permalink
fix: fix genesis and support abstain (#545)
Browse files Browse the repository at this point in the history
* test: replace hardcoded literal

* fix: fix init genesis

* fix: support abstain

* docs: update CHANGELOG.md

* test: add tests on ReceiveFromTreasuryAuthorization

* fix: fix under estimation of undecided count
  • Loading branch information
0Tech authored Jun 2, 2022
1 parent 7c3db3b commit ac918ac
Show file tree
Hide file tree
Showing 13 changed files with 483 additions and 98 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (client) [\#476](https://github.com/line/lbm-sdk/pull/476) change the default value of the client output format in the config
* (server/grpc) [\#516](https://github.com/line/lbm-sdk/pull/516) restore build norace flag
* (genesis) [\#517](https://github.com/line/lbm-sdk/pull/517) fix genesis auth account format(cosmos-sdk style -> lbm-sdk style)
* (x/foundation) [\#545](https://github.com/line/lbm-sdk/pull/545) fix genesis and support abstain

### Breaking Changes

Expand Down
39 changes: 39 additions & 0 deletions x/foundation/authz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package foundation_test

import (
"testing"

sdk "github.com/line/lbm-sdk/types"
"github.com/line/lbm-sdk/x/foundation"
"github.com/stretchr/testify/require"
)

func TestReceiveFromTreasuryAuthorization(t *testing.T) {
testCases := map[string]struct{
msg sdk.Msg
valid bool
accept bool
}{
"valid": {
msg: &foundation.MsgWithdrawFromTreasury{},
valid: true,
accept: true,
},
"msg mismatch": {
msg: &foundation.MsgVote{},
},
}

for name, tc := range testCases {
authorization := &foundation.ReceiveFromTreasuryAuthorization{}

resp, err := authorization.Accept(sdk.Context{}, tc.msg)
if !tc.valid {
require.Error(t, err, name)
continue
}
require.NoError(t, err, name)

require.Equal(t, tc.accept, resp.Accept)
}
}
171 changes: 94 additions & 77 deletions x/foundation/foundation.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,12 @@ import (
)

func DefaultDecisionPolicy(config Config) DecisionPolicy {
policy := &ThresholdDecisionPolicy{
return &ThresholdDecisionPolicy{
Threshold: config.MinThreshold,
Windows: &DecisionPolicyWindows{
VotingPeriod: 24 * time.Hour,
},
}

// check whether the default policy is valid
if err := policy.ValidateBasic(); err != nil {
panic(err)
}
if err := policy.Validate(config); err != nil {
panic(err)
}

return policy
}

func validateProposers(proposers []string) error {
Expand Down Expand Up @@ -119,6 +109,20 @@ type DecisionPolicy interface {
Validate(config Config) error
}

// DefaultTallyResult returns a TallyResult with all counts set to 0.
func DefaultTallyResult() TallyResult {
return NewTallyResult(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec())
}

func NewTallyResult(yes, abstain, no, veto sdk.Dec) TallyResult {
return TallyResult{
YesCount: yes,
AbstainCount: abstain,
NoCount: no,
NoWithVetoCount: veto,
}
}

func (t *TallyResult) Add(option VoteOption) error {
weight := sdk.OneDec()

Expand Down Expand Up @@ -221,49 +225,6 @@ func SetMsgs(msgs []sdk.Msg) ([]*codectypes.Any, error) {
return anys, nil
}

var _ codectypes.UnpackInterfacesMessage = (*FoundationInfo)(nil)

func (i FoundationInfo) GetDecisionPolicy() DecisionPolicy {
if i.DecisionPolicy == nil {
return nil
}

policy, ok := i.DecisionPolicy.GetCachedValue().(DecisionPolicy)
if !ok {
return nil
}
return policy
}

func (i *FoundationInfo) SetDecisionPolicy(policy DecisionPolicy) error {
msg, ok := policy.(proto.Message)
if !ok {
return sdkerrors.ErrInvalidType.Wrapf("can't proto marshal %T", msg)
}

any, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return err
}
i.DecisionPolicy = any

return nil
}

// for the tests
func (i FoundationInfo) WithDecisionPolicy(policy DecisionPolicy) *FoundationInfo {
info := i
if err := info.SetDecisionPolicy(policy); err != nil {
return nil
}
return &info
}

func (i *FoundationInfo) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
var policy DecisionPolicy
return unpacker.UnpackAny(i.DecisionPolicy, &policy)
}

var _ DecisionPolicy = (*ThresholdDecisionPolicy)(nil)

func validateDecisionPolicyWindows(windows DecisionPolicyWindows, config Config) error {
Expand All @@ -273,13 +234,9 @@ func validateDecisionPolicyWindows(windows DecisionPolicyWindows, config Config)
return nil
}

func (p ThresholdDecisionPolicy) Validate(config Config) error {
if p.Threshold.LT(config.MinThreshold) {
return sdkerrors.ErrInvalidRequest.Wrap("threshold must be greater than or equal to min_threshold")
}

if err := validateDecisionPolicyWindows(*p.Windows, config); err != nil {
return err
func validateDecisionPolicyWindowsBasic(windows *DecisionPolicyWindows) error {
if windows == nil || windows.VotingPeriod == 0 {
return sdkerrors.ErrInvalidRequest.Wrap("voting period cannot be zero")
}

return nil
Expand Down Expand Up @@ -307,10 +264,10 @@ func (p ThresholdDecisionPolicy) Allow(result TallyResult, totalWeight sdk.Dec,
// plus all undecided count (supposing they all vote yes).
maxYesCount := result.YesCount.Add(undecided)
if maxYesCount.LT(realThreshold) {
return &DecisionPolicyResult{Allow: false, Final: true}, nil
return &DecisionPolicyResult{Final: true}, nil
}

return &DecisionPolicyResult{Final: false}, nil
return &DecisionPolicyResult{}, nil
}

func (p ThresholdDecisionPolicy) GetVotingPeriod() time.Duration {
Expand All @@ -322,18 +279,15 @@ func (p ThresholdDecisionPolicy) ValidateBasic() error {
return sdkerrors.ErrInvalidRequest.Wrap("threshold must be a positive number")
}

if p.Windows == nil || p.Windows.VotingPeriod == 0 {
return sdkerrors.ErrInvalidRequest.Wrap("voting period cannot be zero")
if err := validateDecisionPolicyWindowsBasic(p.Windows); err != nil {
return err
}

return nil
}

var _ DecisionPolicy = (*PercentageDecisionPolicy)(nil)

func (p PercentageDecisionPolicy) Validate(config Config) error {
if p.Percentage.LT(config.MinPercentage) {
return sdkerrors.ErrInvalidRequest.Wrap("percentage must be greater than or equal to min_percentage")
func (p ThresholdDecisionPolicy) Validate(config Config) error {
if p.Threshold.LT(config.MinThreshold) {
return sdkerrors.ErrInvalidRequest.Wrap("threshold must be greater than or equal to min_threshold")
}

if err := validateDecisionPolicyWindows(*p.Windows, config); err != nil {
Expand All @@ -343,34 +297,42 @@ func (p PercentageDecisionPolicy) Validate(config Config) error {
return nil
}

var _ DecisionPolicy = (*PercentageDecisionPolicy)(nil)

func (p PercentageDecisionPolicy) Allow(result TallyResult, totalWeight sdk.Dec, sinceSubmission time.Duration) (*DecisionPolicyResult, error) {
if sinceSubmission < p.Windows.MinExecutionPeriod {
return nil, sdkerrors.ErrUnauthorized.Wrapf("must wait %s after submission before execution, currently at %s", p.Windows.MinExecutionPeriod, sinceSubmission)
}

yesPercentage := result.YesCount.Quo(totalWeight)
notAbstaining := totalWeight.Sub(result.AbstainCount)
// If no one votes (everyone abstains), proposal fails
if notAbstaining.IsZero() {
return &DecisionPolicyResult{Final: true}, nil
}

yesPercentage := result.YesCount.Quo(notAbstaining)
if yesPercentage.GTE(p.Percentage) {
return &DecisionPolicyResult{Allow: true, Final: true}, nil
}

totalCounts := result.TotalCounts()
undecided := totalWeight.Sub(totalCounts)
maxYesCount := result.YesCount.Add(undecided)
maxYesPercentage := maxYesCount.Quo(totalWeight)
maxYesPercentage := maxYesCount.Quo(notAbstaining)
if maxYesPercentage.LT(p.Percentage) {
return &DecisionPolicyResult{Allow: false, Final: true}, nil
return &DecisionPolicyResult{Final: true}, nil
}

return &DecisionPolicyResult{Allow: false, Final: false}, nil
return &DecisionPolicyResult{}, nil
}

func (p PercentageDecisionPolicy) GetVotingPeriod() time.Duration {
return p.Windows.VotingPeriod
}

func (p PercentageDecisionPolicy) ValidateBasic() error {
if p.Windows == nil || p.Windows.VotingPeriod == 0 {
return sdkerrors.ErrInvalidRequest.Wrap("voting period cannot be zero")
if err := validateDecisionPolicyWindowsBasic(p.Windows); err != nil {
return err
}

if err := validateRatio(p.Percentage, "percentage"); err != nil {
Expand All @@ -380,6 +342,18 @@ func (p PercentageDecisionPolicy) ValidateBasic() error {
return nil
}

func (p PercentageDecisionPolicy) Validate(config Config) error {
if p.Percentage.LT(config.MinPercentage) {
return sdkerrors.ErrInvalidRequest.Wrap("percentage must be greater than or equal to min_percentage")
}

if err := validateDecisionPolicyWindows(*p.Windows, config); err != nil {
return err
}

return nil
}

func validateRatio(ratio sdk.Dec, name string) error {
if ratio.IsNil() {
return sdkerrors.ErrInvalidRequest.Wrapf("%s is nil", name)
Expand All @@ -391,6 +365,49 @@ func validateRatio(ratio sdk.Dec, name string) error {
return nil
}

var _ codectypes.UnpackInterfacesMessage = (*FoundationInfo)(nil)

func (i FoundationInfo) GetDecisionPolicy() DecisionPolicy {
if i.DecisionPolicy == nil {
return nil
}

policy, ok := i.DecisionPolicy.GetCachedValue().(DecisionPolicy)
if !ok {
return nil
}
return policy
}

func (i *FoundationInfo) SetDecisionPolicy(policy DecisionPolicy) error {
msg, ok := policy.(proto.Message)
if !ok {
return sdkerrors.ErrInvalidType.Wrapf("can't proto marshal %T", msg)
}

any, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return err
}
i.DecisionPolicy = any

return nil
}

// for the tests
func (i FoundationInfo) WithDecisionPolicy(policy DecisionPolicy) *FoundationInfo {
info := i
if err := info.SetDecisionPolicy(policy); err != nil {
return nil
}
return &info
}

func (i *FoundationInfo) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
var policy DecisionPolicy
return unpacker.UnpackAny(i.DecisionPolicy, &policy)
}

func GetAuthorization(any *codectypes.Any, name string) (Authorization, error) {
cached := any.GetCachedValue()
if cached == nil {
Expand Down
Loading

0 comments on commit ac918ac

Please sign in to comment.