governance: do not allow `approval` hex interpret (#302)

* governance: do not allow `approval` hex interpret
* gov: remove privacy, add reason, upd example
* gov: remove outdated comment
dependabot/go_modules/gopkg.in/yaml.v3-3.0.0 v1.4.2
Max 1 year ago committed by GitHub
parent aecd7314c0
commit c38f4e498c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      README.md
  2. 11
      cmd/subcommands/governance.go
  3. 3
      cmd/subcommands/values.go
  4. 14
      pkg/governance/eip712.go
  5. 68
      pkg/governance/types.go

@ -129,8 +129,7 @@ Check README for details on json file format.
21. Vote on a governance proposal on https://snapshot.org 21. Vote on a governance proposal on https://snapshot.org
./hmy governance vote-proposal --space=[harmony-mainnet.eth] \ ./hmy governance vote-proposal --space=[harmony-mainnet.eth] \
--proposal=<PROPOSAL_IPFS_HASH> --proposal-type=[single-choice] \ --proposal=<PROPOSAL_IPFS_HASH> --proposal-type=[single-choice] \
--choice=<VOTING_CHOICE(S)> --app=[APP] --key=<ACCOUNT_ADDRESS_OR_NAME> \ --choice=<VOTING_CHOICE(S)> --app=[APP] --key=<ACCOUNT_ADDRESS_OR_NAME>
--privacy=[PRIVACY TYPE]
PS: key must first use (hmy keys import-private-key) to import PS: key must first use (hmy keys import-private-key) to import
22. Enter Console 22. Enter Console

@ -35,8 +35,9 @@ func commandVote() (cmd *cobra.Command) {
var choice string var choice string
var key string var key string
var proposalType string var proposalType string
var privacy string // var privacy string
var app string var app string
var reason string
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "vote-proposal", Use: "vote-proposal",
@ -63,9 +64,10 @@ func commandVote() (cmd *cobra.Command) {
Proposal: proposal, Proposal: proposal,
ProposalType: proposalType, ProposalType: proposalType,
Choice: choice, Choice: choice,
Privacy: privacy, // Privacy: privacy,
App: app, App: app,
From: account.Address.Hex(), From: account.Address.Hex(),
Reason: reason,
}) })
}, },
} }
@ -75,8 +77,9 @@ func commandVote() (cmd *cobra.Command) {
cmd.Flags().StringVar(&proposal, "proposal", "", "Proposal hash") cmd.Flags().StringVar(&proposal, "proposal", "", "Proposal hash")
cmd.Flags().StringVar(&proposalType, "proposal-type", "single-choice", "Proposal type like single-choice, approval, quadratic, etc.") cmd.Flags().StringVar(&proposalType, "proposal-type", "single-choice", "Proposal type like single-choice, approval, quadratic, etc.")
cmd.Flags().StringVar(&choice, "choice", "", "Vote choice either as integer, list of integers (e.x. when using ranked choice voting), or string") cmd.Flags().StringVar(&choice, "choice", "", "Vote choice either as integer, list of integers (e.x. when using ranked choice voting), or string")
cmd.Flags().StringVar(&privacy, "privacy", "", "Vote privacy ex. shutter") // cmd.Flags().StringVar(&privacy, "privacy", "", "Vote privacy e.x. shutter")
cmd.Flags().StringVar(&app, "app", "", "Voting app") cmd.Flags().StringVar(&app, "app", "snapshot", "Voting app")
cmd.Flags().StringVar(&reason, "reason", "", "Reason for your choice")
cmd.Flags().BoolVar(&userProvidesPassphrase, "passphrase", false, ppPrompt) cmd.Flags().BoolVar(&userProvidesPassphrase, "passphrase", false, ppPrompt)
cmd.MarkFlagRequired("key") cmd.MarkFlagRequired("key")

@ -107,8 +107,7 @@ Check README for details on json file format.
%s %s
./hmy governance vote-proposal --space=[harmony-mainnet.eth] \ ./hmy governance vote-proposal --space=[harmony-mainnet.eth] \
--proposal=<PROPOSAL_IPFS_HASH> --proposal-type=[single-choice] \ --proposal=<PROPOSAL_IPFS_HASH> --proposal-type=[single-choice] \
--choice=<VOTING_CHOICE(S)> --app=[APP] --key=<ACCOUNT_ADDRESS_OR_NAME> \ --choice=<VOTING_CHOICE(S)> --app=[APP] --key=<ACCOUNT_ADDRESS_OR_NAME>
--privacy=[PRIVACY TYPE]
PS: key must first use (hmy keys import-private-key) to import PS: key must first use (hmy keys import-private-key) to import
%s %s

@ -144,18 +144,32 @@ func (typedData *TypedData) String() (string, error) {
"proposal": typedData.Message["proposal"], "proposal": typedData.Message["proposal"],
"choice": typedData.Message["choice"], "choice": typedData.Message["choice"],
"app": typedData.Message["app"], "app": typedData.Message["app"],
"reason": typedData.Message["reason"],
// this conversion is required to stop snapshot // this conversion is required to stop snapshot
// from complaining about `wrong envelope format` // from complaining about `wrong envelope format`
"timestamp": ts, "timestamp": ts,
"from": typedData.Message["from"], "from": typedData.Message["from"],
}, },
} }
// same comment as above
if typedData.Types["Vote"][4].Type == "uint32" { if typedData.Types["Vote"][4].Type == "uint32" {
if choice, err := toUint64(typedData.Message["choice"]); err != nil { if choice, err := toUint64(typedData.Message["choice"]); err != nil {
return "", errors.Wrapf(err, "choice") return "", errors.Wrapf(err, "choice")
} else { } else {
formatted.Message["choice"] = choice formatted.Message["choice"] = choice
} }
// prevent hex choice interpretation
} else if typedData.Types["Vote"][4].Type == "uint32[]" {
arr := typedData.Message["choice"].([]interface{})
res := make([]uint64, len(arr))
for i, a := range arr {
if c, err := toUint64(a); err != nil {
return "", errors.Wrapf(err, "choice member %d", i)
} else {
res[i] = c
}
}
formatted.Message["choice"] = res
} }
message, err := json.Marshal(formatted) message, err := json.Marshal(formatted)
if err != nil { if err != nil {

@ -13,14 +13,23 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
var (
voteToNumberMapping = map[string]int64{
"for": 1,
"against": 2,
"abstain": 3,
}
)
type Vote struct { type Vote struct {
From string // --key From string // --key
Space string // --space Space string // --space
Proposal string // --proposal Proposal string // --proposal
ProposalType string // --proposal-type ProposalType string // --proposal-type
Choice string // --choice Choice string // --choice
Privacy string // --privacy // Privacy string // --privacy
App string // --app App string // --app
Reason string // --reason
Timestamp int64 // not exposed to the end user Timestamp int64 // not exposed to the end user
} }
@ -65,7 +74,7 @@ func (v *Vote) ToEIP712() (*TypedData, error) {
proposal = v.Proposal proposal = v.Proposal
} }
// vote type, vote choice and vote privacy // vote type, vote choice and vote privacy (TODO)
// choice needs to be converted into its native format for envelope // choice needs to be converted into its native format for envelope
var choice interface{} var choice interface{}
// The space between [1, 2, 3] does not matter since we parse it // The space between [1, 2, 3] does not matter since we parse it
@ -74,7 +83,6 @@ func (v *Vote) ToEIP712() (*TypedData, error) {
// --proposal 0xTruncated \ // --proposal 0xTruncated \
// --proposal-type {"approval","ranked-choice"} \ // --proposal-type {"approval","ranked-choice"} \
// --choice "[1, 2, 3]" \ // --choice "[1, 2, 3]" \
// --app my-app \
// --key <name of pk> // --key <name of pk>
if v.ProposalType == "approval" || v.ProposalType == "ranked-choice" { if v.ProposalType == "approval" || v.ProposalType == "ranked-choice" {
myType = append(myType, eip712.Type{ myType = append(myType, eip712.Type{
@ -93,14 +101,15 @@ func (v *Vote) ToEIP712() (*TypedData, error) {
"unexpected value of choice %s (expected uint32[])", choice, "unexpected value of choice %s (expected uint32[])", choice,
) )
} }
// The space between [1, 2, 3] does not matter to snapshot.org // The space between --choice {value} does not matter to snapshot.org
// But for comparing with the snapshot-js library, remove it // But for comparing with the snapshot-js library, remove it
// hmy governance vote-proposal \ // hmy governance vote-proposal \
// --space harmony-mainnet.eth \ // --space harmony-mainnet.eth \
// --proposal 0xTruncated \ // --proposal 0xTruncated \
// # either quadratic or weighted
// --proposal-type {"quadratic","weighted"} \ // --proposal-type {"quadratic","weighted"} \
// --choice "[1,2,3]" \ // # 20, 20, 40 of my vote (total 80) goes to 1, 2, 3 - note the single / double quotes
// --app my-app \ // --choice '{"1":20,"2":20,"3":40}' \
// --key <name of pk> // --key <name of pk>
} else if v.ProposalType == "quadratic" || v.ProposalType == "weighted" { } else if v.ProposalType == "quadratic" || v.ProposalType == "weighted" {
myType = append(myType, eip712.Type{ myType = append(myType, eip712.Type{
@ -108,26 +117,25 @@ func (v *Vote) ToEIP712() (*TypedData, error) {
Type: "string", Type: "string",
}) })
choice = v.Choice choice = v.Choice
// TODO Untested
// hmy governance vote-proposal \ // hmy governance vote-proposal \
// --space harmony-mainnet.eth \ // --space harmony-mainnet.eth \
// --proposal 0xTruncated \ // --proposal 0xTruncated \
// --proposal-type ANY \ // --proposal-type ANY \
// --choice "unknown-format" \ // --choice "unknown-format" \
// --app my-app \
// --key <name of pk> // --key <name of pk>
// --privacy shutter // --privacy shutter
} else if v.Privacy == "shutter" { // } else if v.Privacy == "shutter" {
myType = append(myType, eip712.Type{ // myType = append(myType, eip712.Type{
Name: "choice", // Name: "choice",
Type: "string", // Type: "string",
}) // })
choice = v.Choice // choice = v.Choice
// hmy governance vote-proposal \ // hmy governance vote-proposal \
// --space harmony-mainnet.eth \ // --space harmony-mainnet.eth \
// --proposal 0xTruncated \ // --proposal 0xTruncated \
// --proposal-type single-choice \ // --proposal-type single-choice \
// --choice 1 \ // --choice 1 \
// --app my-app \
// --key <name of pk> // --key <name of pk>
} else if v.ProposalType == "single-choice" { } else if v.ProposalType == "single-choice" {
myType = append(myType, eip712.Type{ myType = append(myType, eip712.Type{
@ -141,6 +149,28 @@ func (v *Vote) ToEIP712() (*TypedData, error) {
} else { } else {
choice = math.NewHexOrDecimal256(int64(x)) choice = math.NewHexOrDecimal256(int64(x))
} }
// hmy governance vote-proposal \
// --space harmony-mainnet.eth \
// --proposal 0xTruncated \
// --proposal-type basic \
// # any character case works
// --choice {aBstAin/agAiNst/for} \
// --key <name of pk>
} else if v.ProposalType == "basic" {
myType = append(myType, eip712.Type{
Name: "choice",
Type: "uint32",
})
if number, ok := voteToNumberMapping[strings.ToLower(v.Choice)]; ok {
choice = math.NewHexOrDecimal256(number)
} else {
return nil, errors.New(
fmt.Sprintf(
"unknown basic choice %s",
v.Choice,
),
)
}
} else { } else {
return nil, errors.New( return nil, errors.New(
fmt.Sprintf( fmt.Sprintf(
@ -150,11 +180,16 @@ func (v *Vote) ToEIP712() (*TypedData, error) {
) )
} }
// order matters so this is added last // order matters so these are added last
myType = append(myType, eip712.Type{
Name: "reason",
Type: "string",
})
myType = append(myType, eip712.Type{ myType = append(myType, eip712.Type{
Name: "app", Name: "app",
Type: "string", Type: "string",
}) })
// metadata is skipped in this code intentionally
if v.Timestamp == 0 { if v.Timestamp == 0 {
v.Timestamp = time.Now().Unix() v.Timestamp = time.Now().Unix()
@ -186,6 +221,7 @@ func (v *Vote) ToEIP712() (*TypedData, error) {
"timestamp": math.NewHexOrDecimal256(v.Timestamp), "timestamp": math.NewHexOrDecimal256(v.Timestamp),
"proposal": proposal, "proposal": proposal,
"choice": choice, "choice": choice,
"reason": v.Reason,
"app": v.App, "app": v.App,
}, },
PrimaryType: "Vote", PrimaryType: "Vote",

Loading…
Cancel
Save