diff --git a/.github/workflows/gnovm.yml b/.github/workflows/gnovm.yml index 8311d113047..7e7586b23d9 100644 --- a/.github/workflows/gnovm.yml +++ b/.github/workflows/gnovm.yml @@ -13,8 +13,6 @@ jobs: uses: ./.github/workflows/main_template.yml with: modulepath: "gnovm" - # in pull requests, append -short so that the CI runs quickly. - tests-extra-args: ${{ github.event_name == 'pull_request' && '-short' || '' }} secrets: codecov-token: ${{ secrets.CODECOV_TOKEN }} fmt: diff --git a/.github/workflows/test_template.yml b/.github/workflows/test_template.yml index ccbae792c78..c7956b4caf4 100644 --- a/.github/workflows/test_template.yml +++ b/.github/workflows/test_template.yml @@ -41,11 +41,14 @@ jobs: # Craft a filter flag based on the module path to avoid expanding coverage on unrelated tags. export filter="-pkg=github.com/gnolang/gno/${{ inputs.modulepath }}/..." + # codecov only supports "boolean" coverage (whether a line is + # covered or not); so using -covermode=count or atomic would be + # pointless here. # XXX: Simplify coverage of txtar - the current setup is a bit # confusing and meticulous. There will be some improvements in Go # 1.23 regarding coverage, so we can use this as a workaround until # then. - go test -covermode=atomic -timeout ${{ inputs.tests-timeout }} ${{ inputs.tests-extra-args }} ./... -test.gocoverdir=$GOCOVERDIR + go test -covermode=set -timeout ${{ inputs.tests-timeout }} ${{ inputs.tests-extra-args }} ./... -test.gocoverdir=$GOCOVERDIR # Print results (set +x; echo 'go coverage results:') diff --git a/examples/gno.land/p/demo/dao/dao.gno b/examples/gno.land/p/demo/dao/dao.gno index f8ea433192f..e3a2ba72c5b 100644 --- a/examples/gno.land/p/demo/dao/dao.gno +++ b/examples/gno.land/p/demo/dao/dao.gno @@ -15,6 +15,7 @@ const ( // that contains the necessary information to // log and generate a valid proposal type ProposalRequest struct { + Title string // the title associated with the proposal Description string // the description associated with the proposal Executor Executor // the proposal executor } diff --git a/examples/gno.land/p/demo/dao/proposals.gno b/examples/gno.land/p/demo/dao/proposals.gno index 5cad679d006..66abcb248c5 100644 --- a/examples/gno.land/p/demo/dao/proposals.gno +++ b/examples/gno.land/p/demo/dao/proposals.gno @@ -16,7 +16,7 @@ var ( Accepted ProposalStatus = "accepted" // proposal gathered quorum NotAccepted ProposalStatus = "not accepted" // proposal failed to gather quorum ExecutionSuccessful ProposalStatus = "execution successful" // proposal is executed successfully - ExecutionFailed ProposalStatus = "execution failed" // proposal is failed during execution + ExecutionFailed ProposalStatus = "execution failed" // proposal has failed during execution ) func (s ProposalStatus) String() string { @@ -42,6 +42,9 @@ type Proposal interface { // Author returns the author of the proposal Author() std.Address + // Title returns the title of the proposal + Title() string + // Description returns the description of the proposal Description() string diff --git a/examples/gno.land/p/demo/simpledao/dao.gno b/examples/gno.land/p/demo/simpledao/dao.gno index 7a20237ec3f..837f64a41d6 100644 --- a/examples/gno.land/p/demo/simpledao/dao.gno +++ b/examples/gno.land/p/demo/simpledao/dao.gno @@ -3,6 +3,7 @@ package simpledao import ( "errors" "std" + "strings" "gno.land/p/demo/avl" "gno.land/p/demo/dao" @@ -12,6 +13,7 @@ import ( var ( ErrInvalidExecutor = errors.New("invalid executor provided") + ErrInvalidTitle = errors.New("invalid proposal title provided") ErrInsufficientProposalFunds = errors.New("insufficient funds for proposal") ErrInsufficientExecuteFunds = errors.New("insufficient funds for executing proposal") ErrProposalExecuted = errors.New("proposal already executed") @@ -47,6 +49,11 @@ func (s *SimpleDAO) Propose(request dao.ProposalRequest) (uint64, error) { return 0, ErrInvalidExecutor } + // Make sure the title is set + if strings.TrimSpace(request.Title) == "" { + return 0, ErrInvalidTitle + } + var ( caller = getDAOCaller() sentCoins = std.GetOrigSend() // Get the sent coins, if any @@ -61,6 +68,7 @@ func (s *SimpleDAO) Propose(request dao.ProposalRequest) (uint64, error) { // Create the wrapped proposal prop := &proposal{ author: caller, + title: request.Title, description: request.Description, executor: request.Executor, status: dao.Active, diff --git a/examples/gno.land/p/demo/simpledao/dao_test.gno b/examples/gno.land/p/demo/simpledao/dao_test.gno index fb32895e72f..46251e24dad 100644 --- a/examples/gno.land/p/demo/simpledao/dao_test.gno +++ b/examples/gno.land/p/demo/simpledao/dao_test.gno @@ -45,6 +45,50 @@ func TestSimpleDAO_Propose(t *testing.T) { ) }) + t.Run("invalid title", func(t *testing.T) { + t.Parallel() + + var ( + called = false + cb = func() error { + called = true + + return nil + } + ex = &mockExecutor{ + executeFn: cb, + } + + sentCoins = std.NewCoins( + std.NewCoin( + "ugnot", + minProposalFeeValue, + ), + ) + + ms = &mockMemberStore{ + isMemberFn: func(_ std.Address) bool { + return false + }, + } + s = New(ms) + ) + + std.TestSetOrigSend(sentCoins, std.Coins{}) + + _, err := s.Propose(dao.ProposalRequest{ + Executor: ex, + Title: "", // Set invalid title + }) + uassert.ErrorIs( + t, + err, + ErrInvalidTitle, + ) + + uassert.False(t, called) + }) + t.Run("caller cannot cover fee", func(t *testing.T) { t.Parallel() @@ -58,6 +102,7 @@ func TestSimpleDAO_Propose(t *testing.T) { ex = &mockExecutor{ executeFn: cb, } + title = "Proposal title" sentCoins = std.NewCoins( std.NewCoin( @@ -80,6 +125,7 @@ func TestSimpleDAO_Propose(t *testing.T) { _, err := s.Propose(dao.ProposalRequest{ Executor: ex, + Title: title, }) uassert.ErrorIs( t, @@ -105,6 +151,7 @@ func TestSimpleDAO_Propose(t *testing.T) { executeFn: cb, } description = "Proposal description" + title = "Proposal title" proposer = testutils.TestAddress("proposer") sentCoins = std.NewCoins( @@ -129,6 +176,7 @@ func TestSimpleDAO_Propose(t *testing.T) { // Make sure the proposal was added id, err := s.Propose(dao.ProposalRequest{ + Title: title, Description: description, Executor: ex, }) @@ -141,6 +189,7 @@ func TestSimpleDAO_Propose(t *testing.T) { uassert.Equal(t, proposer.String(), prop.Author().String()) uassert.Equal(t, description, prop.Description()) + uassert.Equal(t, title, prop.Title()) uassert.Equal(t, dao.Active.String(), prop.Status().String()) stats := prop.Stats() diff --git a/examples/gno.land/p/demo/simpledao/propstore.gno b/examples/gno.land/p/demo/simpledao/propstore.gno index 06741d397cb..91f2a883047 100644 --- a/examples/gno.land/p/demo/simpledao/propstore.gno +++ b/examples/gno.land/p/demo/simpledao/propstore.gno @@ -3,6 +3,7 @@ package simpledao import ( "errors" "std" + "strings" "gno.land/p/demo/dao" "gno.land/p/demo/seqid" @@ -18,6 +19,7 @@ const maxRequestProposals = 10 // proposal is the internal simpledao proposal implementation type proposal struct { author std.Address // initiator of the proposal + title string // title of the proposal description string // description of the proposal executor dao.Executor // executor for the proposal @@ -31,6 +33,10 @@ func (p *proposal) Author() std.Address { return p.author } +func (p *proposal) Title() string { + return p.title +} + func (p *proposal) Description() string { return p.description } @@ -63,15 +69,20 @@ func (p *proposal) Render() string { // Fetch the voting stats stats := p.Stats() - output := "" - output += ufmt.Sprintf("Author: %s", p.Author().String()) - output += "\n\n" - output += p.Description() - output += "\n\n" - output += ufmt.Sprintf("Status: %s", p.Status().String()) - output += "\n\n" - output += ufmt.Sprintf( - "Voting stats: YES %d (%d%%), NO %d (%d%%), ABSTAIN %d (%d%%), MISSING VOTE %d (%d%%)", + var out string + + out += "## Description\n\n" + if strings.TrimSpace(p.description) != "" { + out += ufmt.Sprintf("%s\n\n", p.description) + } else { + out += "No description provided.\n\n" + } + + out += "## Proposal information\n\n" + out += ufmt.Sprintf("**Status: %s**\n\n", strings.ToUpper(p.Status().String())) + + out += ufmt.Sprintf( + "**Voting stats:**\n- YES %d (%d%%)\n- NO %d (%d%%)\n- ABSTAIN %d (%d%%)\n- MISSING VOTES %d (%d%%)\n", stats.YayVotes, stats.YayPercent(), stats.NayVotes, @@ -81,10 +92,13 @@ func (p *proposal) Render() string { stats.MissingVotes(), stats.MissingVotesPercent(), ) - output += "\n\n" - output += ufmt.Sprintf("Threshold met: %t", stats.YayVotes > (2*stats.TotalVotingPower)/3) - return output + out += "\n\n" + thresholdOut := strings.ToUpper(ufmt.Sprintf("%t", stats.YayVotes > (2*stats.TotalVotingPower)/3)) + + out += ufmt.Sprintf("**Threshold met: %s**\n\n", thresholdOut) + + return out } // addProposal adds a new simpledao proposal to the store diff --git a/examples/gno.land/r/gov/dao/v2/dao.gno b/examples/gno.land/r/gov/dao/v2/dao.gno index d99a161bcdf..9263d8d440b 100644 --- a/examples/gno.land/r/gov/dao/v2/dao.gno +++ b/examples/gno.land/r/gov/dao/v2/dao.gno @@ -2,12 +2,10 @@ package govdao import ( "std" - "strconv" "gno.land/p/demo/dao" "gno.land/p/demo/membstore" "gno.land/p/demo/simpledao" - "gno.land/p/demo/ufmt" ) var ( @@ -65,55 +63,3 @@ func GetPropStore() dao.PropStore { func GetMembStore() membstore.MemberStore { return members } - -func Render(path string) string { - if path == "" { - numProposals := d.Size() - - if numProposals == 0 { - return "No proposals found :(" // corner case - } - - output := "" - - offset := uint64(0) - if numProposals >= 10 { - offset = uint64(numProposals) - 10 - } - - // Fetch the last 10 proposals - for idx, prop := range d.Proposals(offset, uint64(10)) { - output += ufmt.Sprintf( - "- [Proposal #%d](%s:%d) - (**%s**)(by %s)\n", - idx, - "/r/gov/dao/v2", - idx, - prop.Status().String(), - prop.Author().String(), - ) - } - - return output - } - - // Display the detailed proposal - idx, err := strconv.Atoi(path) - if err != nil { - return "404: Invalid proposal ID" - } - - // Fetch the proposal - prop, err := d.ProposalByID(uint64(idx)) - if err != nil { - return ufmt.Sprintf("unable to fetch proposal, %s", err.Error()) - } - - // Render the proposal - output := "" - output += ufmt.Sprintf("# Prop #%d", idx) - output += "\n\n" - output += prop.Render() - output += "\n\n" - - return output -} diff --git a/examples/gno.land/r/gov/dao/v2/gno.mod b/examples/gno.land/r/gov/dao/v2/gno.mod index bc379bf18df..4da6e0a2484 100644 --- a/examples/gno.land/r/gov/dao/v2/gno.mod +++ b/examples/gno.land/r/gov/dao/v2/gno.mod @@ -7,4 +7,6 @@ require ( gno.land/p/demo/simpledao v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/gov/executor v0.0.0-latest + gno.land/p/moul/txlink v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno index 7b25eeb1db3..7d8975e1fe8 100644 --- a/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno @@ -42,9 +42,11 @@ func init() { executor := validators.NewPropExecutor(changesFn) // Create a proposal + title := "Valset change" description := "manual valset changes proposal example" prop := dao.ProposalRequest{ + Title: title, Description: description, Executor: executor, } @@ -73,52 +75,98 @@ func main() { // Output: // -- -// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// # GovDAO Proposals +// +// ## [Prop #0 - Valset change](/r/gov/dao/v2:0) +// +// **Status: ACTIVE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// // // -- -// # Prop #0 +// # Proposal #0 - Valset change // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // manual valset changes proposal example // -// Status: active +// ## Proposal information +// +// **Status: ACTIVE** // -// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%) +// **Voting stats:** +// - YES 0 (0%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 10 (100%) // -// Threshold met: false +// +// **Threshold met: FALSE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// #### [[Vote YES](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=YES)] - [[Vote NO](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=NO)] - [[Vote ABSTAIN](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=ABSTAIN)] // // // -- // -- -// # Prop #0 +// # Proposal #0 - Valset change // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // manual valset changes proposal example // -// Status: accepted +// ## Proposal information +// +// **Status: ACCEPTED** // -// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) // -// Threshold met: true +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// The voting period for this proposal is over. // // // -- // No valset changes to apply. // -- // -- -// # Prop #0 +// # Proposal #0 - Valset change // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // manual valset changes proposal example // -// Status: execution successful +// ## Proposal information +// +// **Status: EXECUTION SUCCESSFUL** +// +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) +// +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** // -// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) +// ### Actions // -// Threshold met: true +// The voting period for this proposal is over. // // // -- diff --git a/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno index 4eb993b80dc..84a64bc4ee2 100644 --- a/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno @@ -19,9 +19,11 @@ func init() { ) // Create a proposal + title := "govdao blog post title" description := "post a new blogpost about govdao" prop := dao.ProposalRequest{ + Title: title, Description: description, Executor: ex, } @@ -50,35 +52,68 @@ func main() { // Output: // -- -// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// # GovDAO Proposals +// +// ## [Prop #0 - govdao blog post title](/r/gov/dao/v2:0) +// +// **Status: ACTIVE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// // // -- -// # Prop #0 +// # Proposal #0 - govdao blog post title // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // post a new blogpost about govdao // -// Status: active +// ## Proposal information +// +// **Status: ACTIVE** // -// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%) +// **Voting stats:** +// - YES 0 (0%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 10 (100%) // -// Threshold met: false +// +// **Threshold met: FALSE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// #### [[Vote YES](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=YES)] - [[Vote NO](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=NO)] - [[Vote ABSTAIN](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=ABSTAIN)] // // // -- // -- -// # Prop #0 +// # Proposal #0 - govdao blog post title // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // post a new blogpost about govdao // -// Status: accepted +// ## Proposal information +// +// **Status: ACCEPTED** // -// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) // -// Threshold met: true +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// The voting period for this proposal is over. // // // -- @@ -87,17 +122,30 @@ func main() { // No posts. // -- // -- -// # Prop #0 +// # Proposal #0 - govdao blog post title // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // post a new blogpost about govdao // -// Status: execution successful +// ## Proposal information +// +// **Status: EXECUTION SUCCESSFUL** +// +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) +// +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** // -// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) +// ### Actions // -// Threshold met: true +// The voting period for this proposal is over. // // // -- diff --git a/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno index 546213431e4..068f520e7e2 100644 --- a/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno @@ -28,9 +28,11 @@ func init() { } // Create a proposal + title := "new govdao member addition" description := "add new members to the govdao" prop := dao.ProposalRequest{ + Title: title, Description: description, Executor: govdao.NewMemberPropExecutor(memberFn), } @@ -65,57 +67,117 @@ func main() { // -- // 1 // -- -// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// # GovDAO Proposals +// +// ## [Prop #0 - new govdao member addition](/r/gov/dao/v2:0) +// +// **Status: ACTIVE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// // // -- -// # Prop #0 +// # Proposal #0 - new govdao member addition // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // add new members to the govdao // -// Status: active +// ## Proposal information +// +// **Status: ACTIVE** +// +// **Voting stats:** +// - YES 0 (0%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 10 (100%) +// // -// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%) +// **Threshold met: FALSE** // -// Threshold met: false +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// #### [[Vote YES](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=YES)] - [[Vote NO](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=NO)] - [[Vote ABSTAIN](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=ABSTAIN)] // // // -- // -- -// # Prop #0 +// # Proposal #0 - new govdao member addition // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // add new members to the govdao // -// Status: accepted +// ## Proposal information +// +// **Status: ACCEPTED** +// +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) +// +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** // -// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) +// ### Actions // -// Threshold met: true +// The voting period for this proposal is over. // // // -- -// - [Proposal #0](/r/gov/dao/v2:0) - (**accepted**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// # GovDAO Proposals +// +// ## [Prop #0 - new govdao member addition](/r/gov/dao/v2:0) +// +// **Status: ACCEPTED** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// // // -- // -- -// # Prop #0 +// # Proposal #0 - new govdao member addition // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // add new members to the govdao // -// Status: execution successful +// ## Proposal information +// +// **Status: EXECUTION SUCCESSFUL** +// +// **Voting stats:** +// - YES 10 (25%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 30 (75%) +// // -// Voting stats: YES 10 (25%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 30 (75%) +// **Threshold met: FALSE** // -// Threshold met: false +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// The voting period for this proposal is over. // // // -- -// - [Proposal #0](/r/gov/dao/v2:0) - (**execution successful**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// # GovDAO Proposals +// +// ## [Prop #0 - new govdao member addition](/r/gov/dao/v2:0) +// +// **Status: EXECUTION SUCCESSFUL** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// // // -- // 4 diff --git a/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno index 8eff79ffb5a..13ca572c512 100644 --- a/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno @@ -9,8 +9,10 @@ import ( func init() { mExec := params.NewStringPropExecutor("prop1.string", "value1") + title := "Setting prop1.string param" comment := "setting prop1.string param" prop := dao.ProposalRequest{ + Title: title, Description: comment, Executor: mExec, } @@ -36,124 +38,95 @@ func main() { // Output: // new prop 0 // -- -// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// # GovDAO Proposals +// +// ## [Prop #0 - Setting prop1.string param](/r/gov/dao/v2:0) +// +// **Status: ACTIVE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// // // -- -// # Prop #0 +// # Proposal #0 - Setting prop1.string param // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // setting prop1.string param // -// Status: active +// ## Proposal information +// +// **Status: ACTIVE** // -// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%) +// **Voting stats:** +// - YES 0 (0%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 10 (100%) // -// Threshold met: false +// +// **Threshold met: FALSE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// #### [[Vote YES](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=YES)] - [[Vote NO](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=NO)] - [[Vote ABSTAIN](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=ABSTAIN)] // // // -- // -- -// # Prop #0 +// # Proposal #0 - Setting prop1.string param // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // setting prop1.string param // -// Status: accepted +// ## Proposal information +// +// **Status: ACCEPTED** // -// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) // -// Threshold met: true +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// The voting period for this proposal is over. // // // -- // -- -// # Prop #0 +// # Proposal #0 - Setting prop1.string param // -// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// ## Description // // setting prop1.string param // -// Status: execution successful +// ## Proposal information // -// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%) +// **Status: EXECUTION SUCCESSFUL** // -// Threshold met: true +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) +// +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// The voting period for this proposal is over. // // - -// Events: -// [ -// { -// "type": "ProposalAdded", -// "attrs": [ -// { -// "key": "proposal-id", -// "value": "0" -// }, -// { -// "key": "proposal-author", -// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" -// } -// ], -// "pkg_path": "gno.land/r/gov/dao/v2", -// "func": "EmitProposalAdded" -// }, -// { -// "type": "VoteAdded", -// "attrs": [ -// { -// "key": "proposal-id", -// "value": "0" -// }, -// { -// "key": "author", -// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" -// }, -// { -// "key": "option", -// "value": "YES" -// } -// ], -// "pkg_path": "gno.land/r/gov/dao/v2", -// "func": "EmitVoteAdded" -// }, -// { -// "type": "ProposalAccepted", -// "attrs": [ -// { -// "key": "proposal-id", -// "value": "0" -// } -// ], -// "pkg_path": "gno.land/r/gov/dao/v2", -// "func": "EmitProposalAccepted" -// }, -// { -// "type": "set", -// "attrs": [ -// { -// "key": "k", -// "value": "prop1.string" -// } -// ], -// "pkg_path": "gno.land/r/sys/params", -// "func": "" -// }, -// { -// "type": "ProposalExecuted", -// "attrs": [ -// { -// "key": "proposal-id", -// "value": "0" -// }, -// { -// "key": "exec-status", -// "value": "accepted" -// } -// ], -// "pkg_path": "gno.land/r/gov/dao/v2", -// "func": "ExecuteProposal" -// } -// ] diff --git a/examples/gno.land/r/gov/dao/v2/render.gno b/examples/gno.land/r/gov/dao/v2/render.gno new file mode 100644 index 00000000000..4cca397e851 --- /dev/null +++ b/examples/gno.land/r/gov/dao/v2/render.gno @@ -0,0 +1,123 @@ +package govdao + +import ( + "strconv" + "strings" + + "gno.land/p/demo/dao" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/txlink" + "gno.land/r/demo/users" +) + +func Render(path string) string { + var out string + + if path == "" { + out += "# GovDAO Proposals\n\n" + numProposals := d.Size() + + if numProposals == 0 { + out += "No proposals found :(" // corner case + return out + } + + offset := uint64(0) + if numProposals >= 10 { + offset = uint64(numProposals) - 10 + } + + // Fetch the last 10 proposals + proposals := d.Proposals(offset, uint64(10)) + for i := len(proposals) - 1; i >= 0; i-- { + prop := proposals[i] + + title := prop.Title() + if len(title) > 40 { + title = title[:40] + "..." + } + + propID := offset + uint64(i) + out += ufmt.Sprintf("## [Prop #%d - %s](/r/gov/dao/v2:%d)\n\n", propID, title, propID) + out += ufmt.Sprintf("**Status: %s**\n\n", strings.ToUpper(prop.Status().String())) + + user := users.GetUserByAddress(prop.Author()) + authorDisplayText := prop.Author().String() + if user != nil { + authorDisplayText = ufmt.Sprintf("[%s](/r/demo/users:%s)", user.Name, user.Name) + } + + out += ufmt.Sprintf("**Author: %s**\n\n", authorDisplayText) + + if i != 0 { + out += "---\n\n" + } + } + + return out + } + + // Display the detailed proposal + idx, err := strconv.Atoi(path) + if err != nil { + return "404: Invalid proposal ID" + } + + // Fetch the proposal + prop, err := d.ProposalByID(uint64(idx)) + if err != nil { + return ufmt.Sprintf("unable to fetch proposal, %s", err.Error()) + } + + // Render the proposal page + out += renderPropPage(prop, idx) + + return out +} + +func renderPropPage(prop dao.Proposal, idx int) string { + var out string + + out += ufmt.Sprintf("# Proposal #%d - %s\n\n", idx, prop.Title()) + out += prop.Render() + out += renderAuthor(prop) + out += renderActionBar(prop, idx) + out += "\n\n" + + return out +} + +func renderAuthor(p dao.Proposal) string { + var out string + + authorUsername := "" + user := users.GetUserByAddress(p.Author()) + if user != nil { + authorUsername = user.Name + } + + if authorUsername != "" { + out += ufmt.Sprintf("**Author: [%s](/r/demo/users:%s)**\n\n", authorUsername, authorUsername) + } else { + out += ufmt.Sprintf("**Author: %s**\n\n", p.Author().String()) + } + + return out +} + +func renderActionBar(p dao.Proposal, idx int) string { + var out string + + out += "### Actions\n\n" + if p.Status() == dao.Active { + out += ufmt.Sprintf("#### [[Vote YES](%s)] - [[Vote NO](%s)] - [[Vote ABSTAIN](%s)]", + txlink.URL("VoteOnProposal", "id", strconv.Itoa(idx), "option", "YES"), + txlink.URL("VoteOnProposal", "id", strconv.Itoa(idx), "option", "NO"), + txlink.URL("VoteOnProposal", "id", strconv.Itoa(idx), "option", "ABSTAIN"), + ) + } else { + out += "The voting period for this proposal is over." + } + + return out +} diff --git a/gnovm/pkg/gnolang/files_test.go b/gnovm/pkg/gnolang/files_test.go index f1bc87d21d8..09be600b198 100644 --- a/gnovm/pkg/gnolang/files_test.go +++ b/gnovm/pkg/gnolang/files_test.go @@ -33,19 +33,26 @@ func (nopReader) Read(p []byte) (int, error) { return 0, io.EOF } // fix a specific test: // go test -run TestFiles/'^bin1.gno' -short -v -update-golden-tests . func TestFiles(t *testing.T) { + t.Parallel() + rootDir, err := filepath.Abs("../../../") require.NoError(t, err) - opts := &test.TestOptions{ - RootDir: rootDir, - Output: io.Discard, - Error: io.Discard, - Sync: *withSync, + newOpts := func() *test.TestOptions { + o := &test.TestOptions{ + RootDir: rootDir, + Output: io.Discard, + Error: io.Discard, + Sync: *withSync, + } + o.BaseStore, o.TestStore = test.Store( + rootDir, true, + nopReader{}, o.WriterForStore(), io.Discard, + ) + return o } - opts.BaseStore, opts.TestStore = test.Store( - rootDir, true, - nopReader{}, opts.WriterForStore(), io.Discard, - ) + // sharedOpts is used for all "short" tests. + sharedOpts := newOpts() dir := "../../tests/" fsys := os.DirFS(dir) @@ -59,7 +66,8 @@ func TestFiles(t *testing.T) { return nil } subTestName := path[len("files/"):] - if strings.HasSuffix(path, "_long.gno") && testing.Short() { + isLong := strings.HasSuffix(path, "_long.gno") + if isLong && testing.Short() { t.Run(subTestName, func(t *testing.T) { t.Skip("skipping in -short") }) @@ -73,6 +81,12 @@ func TestFiles(t *testing.T) { var criticalError error t.Run(subTestName, func(t *testing.T) { + opts := sharedOpts + if isLong { + // Long tests are run in parallel, and with their own store. + t.Parallel() + opts = newOpts() + } changed, err := opts.RunFiletest(path, content) if err != nil { t.Fatal(err.Error()) @@ -94,16 +108,24 @@ func TestFiles(t *testing.T) { // TestStdlibs tests all the standard library packages. func TestStdlibs(t *testing.T) { + t.Parallel() + rootDir, err := filepath.Abs("../../../") require.NoError(t, err) - var capture bytes.Buffer - out := io.Writer(&capture) - if testing.Verbose() { - out = os.Stdout + newOpts := func() (capture *bytes.Buffer, opts *test.TestOptions) { + var out io.Writer + if testing.Verbose() { + out = os.Stdout + } else { + capture = new(bytes.Buffer) + out = capture + } + opts = test.NewTestOptions(rootDir, nopReader{}, out, out) + opts.Verbose = true + return } - opts := test.NewTestOptions(rootDir, nopReader{}, out, out) - opts.Verbose = true + sharedCapture, sharedOpts := newOpts() dir := "../../stdlibs/" fsys := os.DirFS(dir) @@ -118,12 +140,31 @@ func TestStdlibs(t *testing.T) { fp := filepath.Join(dir, path) memPkg := gnolang.ReadMemPackage(fp, path) t.Run(strings.ReplaceAll(memPkg.Path, "/", "-"), func(t *testing.T) { - if testing.Short() { - switch memPkg.Path { - case "bytes", "strconv", "regexp/syntax": + capture, opts := sharedCapture, sharedOpts + switch memPkg.Path { + // Excluded in short + case + "bufio", + "bytes", + "strconv": + if testing.Short() { t.Skip("Skipped because of -short, and this stdlib is very long currently.") } + fallthrough + // Run using separate store, as it's faster + case + "math/rand", + "regexp", + "regexp/syntax", + "sort": + t.Parallel() + capture, opts = newOpts() + } + + if capture != nil { + capture.Reset() } + err := test.Test(memPkg, "", opts) if !testing.Verbose() { t.Log(capture.String()) diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index 9374db263ee..5de37a68405 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -284,6 +284,8 @@ func (opts *TestOptions) runTestFiles( if opts.Metrics { alloc = gno.NewAllocator(math.MaxInt64) } + // reset store ops, if any - we only need them for some filetests. + opts.TestStore.SetLogStoreOps(false) // Check if we already have the package - it may have been eagerly // loaded. diff --git a/gnovm/stdlibs/bytes/compare_test.gno b/gnovm/stdlibs/bytes/compare_test.gno index f2b1e7c692b..5ebeba33889 100644 --- a/gnovm/stdlibs/bytes/compare_test.gno +++ b/gnovm/stdlibs/bytes/compare_test.gno @@ -66,6 +66,8 @@ func TestCompareIdenticalSlice(t *testing.T) { } func TestCompareBytes(t *testing.T) { + t.Skip("This test takes very long to run on Gno at time of writing, even in its short form") + lengths := make([]int, 0) // lengths to test in ascending order for i := 0; i <= 128; i++ { lengths = append(lengths, i) diff --git a/gnovm/tests/stdlibs/generated.go b/gnovm/tests/stdlibs/generated.go index 2cc904a9170..db5ecdec05d 100644 --- a/gnovm/tests/stdlibs/generated.go +++ b/gnovm/tests/stdlibs/generated.go @@ -9,6 +9,7 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" testlibs_std "github.com/gnolang/gno/gnovm/tests/stdlibs/std" testlibs_testing "github.com/gnolang/gno/gnovm/tests/stdlibs/testing" + testlibs_unicode "github.com/gnolang/gno/gnovm/tests/stdlibs/unicode" ) // NativeFunc represents a function in the standard library which has a native @@ -325,6 +326,118 @@ var nativeFuncs = [...]NativeFunc{ func(m *gno.Machine) { r0 := testlibs_testing.X_unixNano() + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "unicode", + "IsPrint", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("rune")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 rune + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := testlibs_unicode.IsPrint(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "unicode", + "IsGraphic", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("rune")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 rune + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := testlibs_unicode.IsGraphic(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "unicode", + "SimpleFold", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("rune")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("rune")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 rune + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := testlibs_unicode.SimpleFold(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "unicode", + "IsUpper", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("rune")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 rune + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := testlibs_unicode.IsUpper(p0) + m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, @@ -337,6 +450,7 @@ var nativeFuncs = [...]NativeFunc{ var initOrder = [...]string{ "std", "testing", + "unicode", } // InitOrder returns the initialization order of the standard libraries. diff --git a/gnovm/tests/stdlibs/unicode/natives.gno b/gnovm/tests/stdlibs/unicode/natives.gno new file mode 100644 index 00000000000..c7efaac70cc --- /dev/null +++ b/gnovm/tests/stdlibs/unicode/natives.gno @@ -0,0 +1,8 @@ +package unicode + +// Optimized as native bindings in tests. + +func IsPrint(r rune) bool +func IsGraphic(r rune) bool +func SimpleFold(r rune) rune +func IsUpper(r rune) bool diff --git a/gnovm/tests/stdlibs/unicode/natives.go b/gnovm/tests/stdlibs/unicode/natives.go new file mode 100644 index 00000000000..e627f4fe6be --- /dev/null +++ b/gnovm/tests/stdlibs/unicode/natives.go @@ -0,0 +1,8 @@ +package unicode + +import "unicode" + +func IsPrint(r rune) bool { return unicode.IsPrint(r) } +func IsGraphic(r rune) bool { return unicode.IsGraphic(r) } +func SimpleFold(r rune) rune { return unicode.SimpleFold(r) } +func IsUpper(r rune) bool { return unicode.IsUpper(r) }