Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func main() {
// Output:
// Valoper's details:
// ## test-1
//
// test-1's description
//
// - Operator Address: g1sp8v98h2gadm5jggtzz9w5ksexqn68ympsd68h
Expand Down
5 changes: 5 additions & 0 deletions examples/gno.land/r/gnops/valopers/proposal/proposal.gno
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
var (
ErrValidatorMissing = errors.New("the validator is missing")
ErrSameValues = errors.New("the valoper has the same voting power and pubkey")
ErrNegativeMinFee = errors.New("min fee must be non-negative")
)

// NewValidatorProposalRequest creates a proposal request to the GovDAO
Expand Down Expand Up @@ -88,5 +89,9 @@ func ProposeNewInstructionsProposalRequest(cur realm, newInstructions string) da
// generic sys/params factory so the fee lives in
// node:valoper:register_fee.
func ProposeNewMinFeeProposalRequest(cur realm, newMinFee int64) dao.ProposalRequest {
if newMinFee < 0 {
panic(ErrNegativeMinFee)
}

return sysparams.NewSysParamUint64PropRequest(cross(cur), "node", "valoper", "register_fee", uint64(newMinFee))
}
8 changes: 8 additions & 0 deletions examples/gno.land/r/gnops/valopers/proposal/proposal_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ func TestValopers_ProposeNewMinFee(cur realm, t *testing.T) {
urequire.Equal(t, description, proposal.Description())
}

func TestValopers_ProposeNewMinFee_RejectsNegative(cur realm, t *testing.T) {
testing.SetRealm(testing.NewUserRealm(g1user))

urequire.AbortsWithMessage(t, cur, ErrNegativeMinFee.Error(), func(cur realm) {
ProposeNewMinFeeProposalRequest(cur, int64(-1))
})
}

/* TODO fix this @moul
func TestValopers_ProposeNewValidator2(cur realm, t *testing.T) {
const (
Expand Down
15 changes: 12 additions & 3 deletions examples/gno.land/r/gnops/valopers/valopers.gno
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"gno.land/p/nt/avl/v0/pager"
"gno.land/p/nt/bptree/v0"
"gno.land/p/nt/combinederr/v0"
"gno.land/p/nt/markdown/sanitize/v0"
"gno.land/p/nt/ownable/v0"
"gno.land/p/nt/ownable/v0/exts/authorizable"
"gno.land/p/nt/ufmt/v0"
Expand Down Expand Up @@ -88,13 +89,19 @@ func (v Valoper) Auth() *authorizable.Authorizable {
}

func AddToAuthList(cur realm, addr address, member address) {
if err := validateBech32(member); err != nil {
panic(err)
}
v := GetByAddr(addr)
if err := v.Auth().AddToAuthList(0, cur, member); err != nil {
panic(err)
}
}

func DeleteFromAuthList(cur realm, addr address, member address) {
if err := validateBech32(member); err != nil {
panic(err)
}
v := GetByAddr(addr)
if err := v.Auth().DeleteFromAuthList(0, cur, member); err != nil {
panic(err)
Expand Down Expand Up @@ -460,10 +467,12 @@ func (v *Valoper) Validate() error {

// Render renders a single valoper with their information.
func (v Valoper) Render() string {
output := ufmt.Sprintf("## %s\n", v.Moniker)
output := ufmt.Sprintf("## %s", v.Moniker)

if v.Description != "" {
output += ufmt.Sprintf("%s\n\n", v.Description)
if body := sanitize.Block(v.Description); body != "" {
output += body
} else {
output += "\n"
}

output += ufmt.Sprintf("- Operator Address: %s\n", v.OperatorAddress.String())
Expand Down
61 changes: 61 additions & 0 deletions examples/gno.land/r/gnops/valopers/valopers_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,67 @@ func TestValopers_UpdateAuthMembers(cur realm, t *testing.T) {
uassert.Equal(t, newMoniker, valoper.Moniker)
})
})

t.Run("invalid member address rejected", func(cur realm, t *testing.T) {
resetState()

info := validValidatorInfo(t)
testing.SetRealm(testing.NewUserRealm(info.Address))
testing.SetOriginSend(chain.Coins{minFee})

uassert.NotPanics(t, cur, func() {
Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})

// Caller is the owner, but the member address is invalid: the
// bech32 guard rejects it before reaching the auth list. Empty
// and malformed-but-nonempty (bad checksum) are both rejected,
// so a future weakening to a mere empty-check would fail here.
for _, bad := range []address{address(""), address("g1nonsense")} {
uassert.AbortsWithMessage(t, cur, ErrInvalidAddress.Error(), func() {
AddToAuthList(cross(cur), info.Address, bad)
})

uassert.AbortsWithMessage(t, cur, ErrInvalidAddress.Error(), func() {
DeleteFromAuthList(cross(cur), info.Address, bad)
})
}
})
}

func TestValopers_RenderSanitizesDescription(cur realm, t *testing.T) {
resetState()

info := validValidatorInfo(t)
// Description that tries to inject a top-level heading. Input
// validation accepts it (only length is checked); Render must
// neutralize the structural markdown before it reaches the profile
// page and the govdao proposal that embeds Render.
info.Description = "# FORGEDHEADING\nbody FORGEDBODY"

testing.SetRealm(testing.NewUserRealm(info.Address))
testing.SetOriginSend(chain.Coins{minFee})
uassert.NotPanics(t, cur, func() {
Register(cross(cur), info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})

out := GetByAddr(info.Address).Render()

// Prose survives.
uassert.True(t, strings.Contains(out, "FORGEDBODY"), "description body must be preserved")
uassert.True(t, strings.Contains(out, "FORGEDHEADING"), "heading text must be preserved as prose")
// But not as a live ATX heading line.
uassert.False(t, strings.Contains(out, "\n# FORGEDHEADING"), "forged heading must be neutralized")

// A non-empty description that sanitizes to empty (lone link-
// reference definition) must not glue the heading onto the chrome.
testing.SetRealm(testing.NewUserRealm(info.Address))
uassert.NotPanics(t, cur, func() {
UpdateDescription(cross(cur), info.Address, "[a]: https://x.example")
})
out2 := GetByAddr(info.Address).Render()
uassert.True(t, strings.Contains(out2, "## "+info.Moniker+"\n"), "heading line must stay terminated")
uassert.True(t, strings.Contains(out2, "\n- Operator Address:"), "operator-address line must not merge into the heading")
}

func TestValopers_UpdateMoniker(cur realm, t *testing.T) {
Expand Down