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

Implement voting right delegation for oracle #148

Merged
merged 4 commits into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions x/oracle/ballot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestPBPower(t *testing.T) {
ballotPower := sdk.ZeroInt()

for i := 0; i < len(mockValset.Validators); i++ {
vote := NewPriceVote(sdk.ZeroDec(), assets.MicroSDRDenom, valAccAddrs[i])
vote := NewPriceVote(sdk.ZeroDec(), assets.MicroSDRDenom, sdk.ValAddress(valAccAddrs[i]))
pb = append(pb, vote)

valPower, err := vote.getPower(input.ctx, mockValset)
Expand All @@ -68,7 +68,7 @@ func TestPBPower(t *testing.T) {
require.Equal(t, ballotPower, pb.power(input.ctx, mockValset))

// Mix in a fake validator, the total power should not have changed.
fakeVote := NewPriceVote(sdk.OneDec(), assets.MicroSDRDenom, addrs[0])
fakeVote := NewPriceVote(sdk.OneDec(), assets.MicroSDRDenom, sdk.ValAddress(addrs[0]))
pb = append(pb, fakeVote)
require.Equal(t, ballotPower, pb.power(input.ctx, mockValset))
}
Expand Down Expand Up @@ -125,7 +125,7 @@ func TestPBWeightedMedian(t *testing.T) {
if tc.isValidator[i] {
mockValset.Validators = append(mockValset.Validators, mockVal)
}
vote := NewPriceVote(sdk.NewDecWithPrec(int64(input*base), int64(oracleDecPrecision)), assets.MicroSDRDenom, valAccAddr)
vote := NewPriceVote(sdk.NewDecWithPrec(int64(input*base), int64(oracleDecPrecision)), assets.MicroSDRDenom, sdk.ValAddress(valAccAddr))
pb = append(pb, vote)
}

Expand Down
29 changes: 29 additions & 0 deletions x/oracle/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,32 @@ func GetCmdQueryParams(queryRoute string, cdc *codec.Codec) *cobra.Command {

return cmd
}

// GetCmdQueryFeederDelegation implements the query feeder delegation command
func GetCmdQueryFeederDelegation(queryRoute string, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: oracle.QueryActive,
hendrikhofstadt marked this conversation as resolved.
Show resolved Hide resolved
Short: "Query the account the validator's voting right is delegated to",
Long: strings.TrimSpace(`
Query the account the validator's voting right is delegated to.

$ terracli query oracle feeder-delegation --validator terravaloper1ifji3ifj
`),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)

res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, oracle.QueryActive), nil)
hendrikhofstadt marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

var actives DenomList
cdc.MustUnmarshalJSON(res, &actives)
return cliCtx.PrintOutput(actives)
},
}

cmd.Flags().String(flagValidator, "", "validator to get the delegation for")

return cmd
}
79 changes: 75 additions & 4 deletions x/oracle/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"fmt"
"github.com/pkg/errors"
"strings"

"github.com/terra-project/core/x/oracle"
Expand All @@ -16,9 +17,11 @@ import (
)

const (
flagDenom = "denom"
flagPrice = "price"
flagVoter = "voter"
flagDenom = "denom"
flagPrice = "price"
flagVoter = "voter"
flagValidator = "validator"
flagDelegate = "delegate"
)

// GetCmdPriceVote will create a send tx and sign it with the given key.
Expand Down Expand Up @@ -59,13 +62,26 @@ where "ukrw" is the denominating currency, and "8890" is the price of micro Luna
return fmt.Errorf("--price flag is required")
}

// By default the voter is voting on behalf of itself
validator := sdk.ValAddress(voter)

// Override validator if flag is set
valStr := viper.GetString(flagValidator)
if len(valStr) != 0 {
parsedVal, err := sdk.ValAddressFromBech32(valStr)
if err != nil {
return errors.Wrap(err, "validator address is invalid")
}
validator = parsedVal
}

// Parse the price to Dec
price, err := sdk.NewDecFromStr(priceStr)
if err != nil {
return fmt.Errorf("given price {%s} is not a valid format; price should be formatted as float", priceStr)
}

msg := oracle.NewMsgPriceFeed(denom, price, voter)
msg := oracle.NewMsgPriceFeed(denom, price, voter, validator)
err = msg.ValidateBasic()
if err != nil {
return err
Expand All @@ -77,6 +93,61 @@ where "ukrw" is the denominating currency, and "8890" is the price of micro Luna

cmd.Flags().String(flagDenom, "", "denominating currency")
cmd.Flags().String(flagPrice, "", "price of Luna in denom currency")
cmd.Flags().String(flagValidator, "", "validator on behalf of which to vote (for delegated feeders)")

return cmd
}

// GetCmdDelegateFeederPermission will create a feeder permission delegation tx and sign it with the given key.
func GetCmdDelegateFeederPermission(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "set-feeder",
Short: "Delegate the permission to vote for the oracle to an address",
Long: strings.TrimSpace(`
Delegate the permission to vote for the oracle to an address.
That way you can keep your validator operator key offline and use a separate replaceable key online.

$ terracli oracle set-feeder --delegate terra1abceuihfu93fud --from mykey

where "terra1abceuihfu93fud" is the address you want to delegate your voting rights to.
`),
RunE: func(cmd *cobra.Command, args []string) error {

txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))
cliCtx := context.NewCLIContext().
WithCodec(cdc).
WithAccountDecoder(cdc)

if err := cliCtx.EnsureAccountExists(); err != nil {
return err
}

// Get from address
voter := cliCtx.GetFromAddress()

// The address the right is being delegated from
validator := sdk.ValAddress(voter)

delegateStr := viper.GetString(flagDelegate)
if len(delegateStr) == 0 {
return fmt.Errorf("--delegate flag is required")
}
delegate, err := sdk.AccAddressFromBech32(delegateStr)
if err != nil {
return errors.Wrap(err, "delegate is not a valid account address")
}

msg := oracle.NewMsgDelegateFeederPermission(validator, delegate)
err = msg.ValidateBasic()
if err != nil {
return err
}

return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false)
},
}

cmd.Flags().String(flagDelegate, "", "account the voting right will be delegated to")

return cmd
}
2 changes: 2 additions & 0 deletions x/oracle/client/module_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command {
cli.GetCmdQueryVotes(mc.storeKey, mc.cdc),
cli.GetCmdQueryActive(mc.storeKey, mc.cdc),
cli.GetCmdQueryParams(mc.storeKey, mc.cdc),
cli.GetCmdQueryFeederDelegation(mc.storeKey, mc.cdc),
)...)

return oracleQueryCmd
Expand All @@ -44,6 +45,7 @@ func (mc ModuleClient) GetTxCmd() *cobra.Command {

oracleTxCmd.AddCommand(client.PostCommands(
cli.GetCmdPriceVote(mc.cdc),
cli.GetCmdDelegateFeederPermission(mc.cdc),
)...)

return oracleTxCmd
Expand Down
2 changes: 1 addition & 1 deletion x/oracle/end_blocker.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func tally(ctx sdk.Context, k Keeper, pb PriceBallot) (weightedMedian sdk.Dec, b
bondSize := validator.GetBondedTokens()

ballotWinners = append(ballotWinners, types.Claim{
Recipient: vote.Voter,
Recipient: sdk.AccAddress(vote.Voter),
Weight: bondSize,
Class: types.OracleClaimClass,
})
Expand Down
34 changes: 18 additions & 16 deletions x/oracle/end_blocker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestOracleThreshold(t *testing.T) {
input, h := setup(t)

// Less than the threshold signs, msg fails
msg := NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[0])
msg := NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[0], sdk.ValAddress(addrs[0]))
res := h(input.ctx, msg)
require.True(t, res.IsOK())

Expand All @@ -43,13 +43,13 @@ func TestOracleThreshold(t *testing.T) {
require.NotNil(t, err)

// More than the threshold signs, msg succeeds
msg = NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[0])
msg = NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[0], sdk.ValAddress(addrs[0]))
h(input.ctx, msg)

msg = NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[1])
msg = NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[1], sdk.ValAddress(addrs[1]))
h(input.ctx, msg)

msg = NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[2])
msg = NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[2], sdk.ValAddress(addrs[2]))
h(input.ctx, msg)

EndBlocker(input.ctx, input.oracleKeeper)
Expand All @@ -63,10 +63,10 @@ func TestOracleThreshold(t *testing.T) {
input.valset.Validators = append(input.valset.Validators, newValidator)
input.oracleKeeper.valset = input.valset

msg = NewMsgPriceFeed(assets.MicroSDRDenom, anotherRandomPrice, addrs[0])
msg = NewMsgPriceFeed(assets.MicroSDRDenom, anotherRandomPrice, addrs[0], sdk.ValAddress(addrs[0]))
h(input.ctx, msg)

msg = NewMsgPriceFeed(assets.MicroSDRDenom, anotherRandomPrice, addrs[1])
msg = NewMsgPriceFeed(assets.MicroSDRDenom, anotherRandomPrice, addrs[1], sdk.ValAddress(addrs[1]))
h(input.ctx, msg)

EndBlocker(input.ctx, input.oracleKeeper)
Expand All @@ -80,27 +80,27 @@ func TestOracleMultiVote(t *testing.T) {
input, h := setup(t)

// Less than the threshold signs, msg fails
msg := NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[0])
msg := NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[0], sdk.ValAddress(addrs[0]))
res := h(input.ctx, msg)
require.True(t, res.IsOK())

msg = NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[1])
msg = NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[1], sdk.ValAddress(addrs[1]))
res = h(input.ctx, msg)
require.True(t, res.IsOK())

msg = NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[2])
msg = NewMsgPriceFeed(assets.MicroSDRDenom, randomPrice, addrs[2], sdk.ValAddress(addrs[2]))
res = h(input.ctx, msg)
require.True(t, res.IsOK())

msg = NewMsgPriceFeed(assets.MicroSDRDenom, anotherRandomPrice, addrs[0])
msg = NewMsgPriceFeed(assets.MicroSDRDenom, anotherRandomPrice, addrs[0], sdk.ValAddress(addrs[0]))
res = h(input.ctx, msg)
require.True(t, res.IsOK())

msg = NewMsgPriceFeed(assets.MicroSDRDenom, anotherRandomPrice, addrs[1])
msg = NewMsgPriceFeed(assets.MicroSDRDenom, anotherRandomPrice, addrs[1], sdk.ValAddress(addrs[1]))
res = h(input.ctx, msg)
require.True(t, res.IsOK())

msg = NewMsgPriceFeed(assets.MicroSDRDenom, anotherRandomPrice, addrs[2])
msg = NewMsgPriceFeed(assets.MicroSDRDenom, anotherRandomPrice, addrs[2], sdk.ValAddress(addrs[2]))
res = h(input.ctx, msg)
require.True(t, res.IsOK())

Expand All @@ -116,7 +116,7 @@ func TestOracleWhitelist(t *testing.T) {
input, h := setup(t)

// Less than the threshold signs, msg fails
msg := NewMsgPriceFeed(assets.MicroKRWDenom, randomPrice, addrs[0])
msg := NewMsgPriceFeed(assets.MicroKRWDenom, randomPrice, addrs[0], sdk.ValAddress(addrs[0]))
res := h(input.ctx, msg)
require.True(t, res.IsOK())

Expand All @@ -130,7 +130,7 @@ func TestOracleDrop(t *testing.T) {
dropThreshold := input.oracleKeeper.GetParams(input.ctx).DropThreshold
input.oracleKeeper.SetLunaSwapRate(input.ctx, assets.MicroKRWDenom, randomPrice)

msg := NewMsgPriceFeed(assets.MicroKRWDenom, randomPrice, addrs[0])
msg := NewMsgPriceFeed(assets.MicroKRWDenom, randomPrice, addrs[0], sdk.ValAddress(addrs[0]))
h(input.ctx, msg)

input.ctx = input.ctx.WithBlockHeight(1)
Expand Down Expand Up @@ -179,9 +179,10 @@ func TestOracleTally(t *testing.T) {
assets.MicroSDRDenom,
decPrice,
valAccAddrs[i],
sdk.ValAddress(valAccAddrs[i]),
)

vote := NewPriceVote(decPrice, assets.MicroSDRDenom, valAccAddrs[i])
vote := NewPriceVote(decPrice, assets.MicroSDRDenom, sdk.ValAddress(valAccAddrs[i]))
ballot = append(ballot, vote)

res := h(input.ctx, pfm)
Expand All @@ -199,7 +200,7 @@ func TestOracleTally(t *testing.T) {

for _, vote := range ballot {
if vote.Price.GTE(weightedMedian.Sub(maxSpread)) && vote.Price.LTE(weightedMedian.Add(maxSpread)) {
rewardees = append(rewardees, vote.Voter)
rewardees = append(rewardees, sdk.AccAddress(vote.Voter))
}
}

Expand All @@ -218,6 +219,7 @@ func TestOracleTallyTiming(t *testing.T) {
assets.MicroSDRDenom,
sdk.OneDec(),
addr,
sdk.ValAddress(addr),
)

res := h(input.ctx, pfm)
Expand Down
18 changes: 12 additions & 6 deletions x/oracle/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import (
const (
DefaultCodespace sdk.CodespaceType = "oracle"

CodeUnknownDenom sdk.CodeType = 1
CodeInvalidPrice sdk.CodeType = 2
CodeVoterNotValidator sdk.CodeType = 3
CodeInvalidVote sdk.CodeType = 4
CodeUnknownDenom sdk.CodeType = 1
CodeInvalidPrice sdk.CodeType = 2
CodeVoterNotValidator sdk.CodeType = 3
CodeInvalidVote sdk.CodeType = 4
CodeNoVotingPermission sdk.CodeType = 5
)

// ----------------------------------------
Expand All @@ -30,11 +31,16 @@ func ErrInvalidPrice(codespace sdk.CodespaceType, price sdk.Dec) sdk.Error {
}

// ErrVoterNotValidator called when the voter is not a validator
func ErrVoterNotValidator(codespace sdk.CodespaceType, voter sdk.AccAddress) sdk.Error {
func ErrVoterNotValidator(codespace sdk.CodespaceType, voter sdk.ValAddress) sdk.Error {
return sdk.NewError(codespace, CodeVoterNotValidator, fmt.Sprintf("Voter is not a validator: %s", voter.String()))
}

// ErrNoVote called when no vote exists
func ErrNoVote(codespace sdk.CodespaceType, voter sdk.AccAddress, denom string) sdk.Error {
func ErrNoVote(codespace sdk.CodespaceType, voter sdk.ValAddress, denom string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidVote, fmt.Sprintf("No vote exists from %s with denom: %s", voter, denom))
}

// ErrNoVotingPermission called when the feeder has no permission to submit a vote for the given operator
func ErrNoVotingPermission(codespace sdk.CodespaceType, feeder sdk.AccAddress, operator sdk.ValAddress) sdk.Error {
return sdk.NewError(codespace, CodeNoVotingPermission, fmt.Sprintf("Feeder %s not permitted to vote on behalf of: %s", feeder.String(), operator.String()))
}
Loading