From 0dc6528589550110a061977bb0e74af9da845a4f Mon Sep 17 00:00:00 2001 From: moul Date: Tue, 3 Dec 2024 14:05:07 +0000 Subject: [PATCH] chore: update portal-loop backup --- portal-loop/README.md | 2 +- ...49.jsonl => backup_portal_loop_txs_6001-6559.jsonl} | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) rename portal-loop/{backup_portal_loop_txs_6001-6549.jsonl => backup_portal_loop_txs_6001-6559.jsonl} (99%) diff --git a/portal-loop/README.md b/portal-loop/README.md index febd83fd..e935f6b2 100644 --- a/portal-loop/README.md +++ b/portal-loop/README.md @@ -2,7 +2,7 @@ ## TXs ``` -6606 +6616 ``` ## addpkgs diff --git a/portal-loop/backup_portal_loop_txs_6001-6549.jsonl b/portal-loop/backup_portal_loop_txs_6001-6559.jsonl similarity index 99% rename from portal-loop/backup_portal_loop_txs_6001-6549.jsonl rename to portal-loop/backup_portal_loop_txs_6001-6559.jsonl index 9716c8ca..e2c01193 100644 --- a/portal-loop/backup_portal_loop_txs_6001-6549.jsonl +++ b/portal-loop/backup_portal_loop_txs_6001-6559.jsonl @@ -547,3 +547,13 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"simpledao","path":"gno.land/p/demo/simpledao","files":[{"name":"dao.gno","body":"package simpledao\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrInvalidExecutor = errors.New(\"invalid executor provided\")\n\tErrInvalidTitle = errors.New(\"invalid proposal title provided\")\n\tErrInsufficientProposalFunds = errors.New(\"insufficient funds for proposal\")\n\tErrInsufficientExecuteFunds = errors.New(\"insufficient funds for executing proposal\")\n\tErrProposalExecuted = errors.New(\"proposal already executed\")\n\tErrProposalInactive = errors.New(\"proposal is inactive\")\n\tErrProposalNotAccepted = errors.New(\"proposal is not accepted\")\n)\n\nvar (\n\tminProposalFeeValue int64 = 100 * 1_000_000 // minimum gnot required for a govdao proposal (100 GNOT)\n\tminExecuteFeeValue int64 = 500 * 1_000_000 // minimum gnot required for a govdao proposal (500 GNOT)\n\n\tminProposalFee = std.NewCoin(\"ugnot\", minProposalFeeValue)\n\tminExecuteFee = std.NewCoin(\"ugnot\", minExecuteFeeValue)\n)\n\n// SimpleDAO is a simple DAO implementation\ntype SimpleDAO struct {\n\tproposals *avl.Tree // seqid.ID -\u003e proposal\n\tmembStore membstore.MemberStore\n}\n\n// New creates a new instance of the simpledao DAO\nfunc New(membStore membstore.MemberStore) *SimpleDAO {\n\treturn \u0026SimpleDAO{\n\t\tproposals: avl.NewTree(),\n\t\tmembStore: membStore,\n\t}\n}\n\nfunc (s *SimpleDAO) Propose(request dao.ProposalRequest) (uint64, error) {\n\t// Make sure the executor is set\n\tif request.Executor == nil {\n\t\treturn 0, ErrInvalidExecutor\n\t}\n\n\t// Make sure the title is set\n\tif strings.TrimSpace(request.Title) == \"\" {\n\t\treturn 0, ErrInvalidTitle\n\t}\n\n\tvar (\n\t\tcaller = getDAOCaller()\n\t\tsentCoins = std.GetOrigSend() // Get the sent coins, if any\n\t\tcanCoverFee = sentCoins.AmountOf(\"ugnot\") \u003e= minProposalFee.Amount\n\t)\n\n\t// Check if the proposal is valid\n\tif !s.membStore.IsMember(caller) \u0026\u0026 !canCoverFee {\n\t\treturn 0, ErrInsufficientProposalFunds\n\t}\n\n\t// Create the wrapped proposal\n\tprop := \u0026proposal{\n\t\tauthor: caller,\n\t\ttitle: request.Title,\n\t\tdescription: request.Description,\n\t\texecutor: request.Executor,\n\t\tstatus: dao.Active,\n\t\ttally: newTally(),\n\t\tgetTotalVotingPowerFn: s.membStore.TotalPower,\n\t}\n\n\t// Add the proposal\n\tid, err := s.addProposal(prop)\n\tif err != nil {\n\t\treturn 0, ufmt.Errorf(\"unable to add proposal, %s\", err.Error())\n\t}\n\n\t// Emit the proposal added event\n\tdao.EmitProposalAdded(id, caller)\n\n\treturn id, nil\n}\n\nfunc (s *SimpleDAO) VoteOnProposal(id uint64, option dao.VoteOption) error {\n\t// Verify the GOVDAO member\n\tcaller := getDAOCaller()\n\n\tmember, err := s.membStore.Member(caller)\n\tif err != nil {\n\t\treturn ufmt.Errorf(\"unable to get govdao member, %s\", err.Error())\n\t}\n\n\t// Check if the proposal exists\n\tpropRaw, err := s.ProposalByID(id)\n\tif err != nil {\n\t\treturn ufmt.Errorf(\"unable to get proposal %d, %s\", id, err.Error())\n\t}\n\n\tprop := propRaw.(*proposal)\n\n\t// Check the proposal status\n\tif prop.Status() == dao.ExecutionSuccessful ||\n\t\tprop.Status() == dao.ExecutionFailed {\n\t\t// Proposal was already executed, nothing to vote on anymore.\n\t\t//\n\t\t// In fact, the proposal should stop accepting\n\t\t// votes as soon as a 2/3+ majority is reached\n\t\t// on either option, but leaving the ability to vote still,\n\t\t// even if a proposal is accepted, or not accepted,\n\t\t// leaves room for \"principle\" vote decisions to be recorded\n\t\treturn ErrProposalInactive\n\t}\n\n\t// Cast the vote\n\tif err = prop.tally.castVote(member, option); err != nil {\n\t\treturn ufmt.Errorf(\"unable to vote on proposal %d, %s\", id, err.Error())\n\t}\n\n\t// Emit the vote cast event\n\tdao.EmitVoteAdded(id, caller, option)\n\n\t// Check the votes to see if quorum is reached\n\tvar (\n\t\ttotalPower = s.membStore.TotalPower()\n\t\tmajorityPower = (2 * totalPower) / 3\n\t)\n\n\tacceptProposal := func() {\n\t\tprop.status = dao.Accepted\n\n\t\tdao.EmitProposalAccepted(id)\n\t}\n\n\tdeclineProposal := func() {\n\t\tprop.status = dao.NotAccepted\n\n\t\tdao.EmitProposalNotAccepted(id)\n\t}\n\n\tswitch {\n\tcase prop.tally.yays \u003e majorityPower:\n\t\t// 2/3+ voted YES\n\t\tacceptProposal()\n\tcase prop.tally.nays \u003e majorityPower:\n\t\t// 2/3+ voted NO\n\t\tdeclineProposal()\n\tcase prop.tally.abstains \u003e majorityPower:\n\t\t// 2/3+ voted ABSTAIN\n\t\tdeclineProposal()\n\tcase prop.tally.yays+prop.tally.nays+prop.tally.abstains \u003e= totalPower:\n\t\t// Everyone voted, but it's undecided,\n\t\t// hence the proposal can't go through\n\t\tdeclineProposal()\n\tdefault:\n\t\t// Quorum not reached\n\t}\n\n\treturn nil\n}\n\nfunc (s *SimpleDAO) ExecuteProposal(id uint64) error {\n\tvar (\n\t\tcaller = getDAOCaller()\n\t\tsentCoins = std.GetOrigSend() // Get the sent coins, if any\n\t\tcanCoverFee = sentCoins.AmountOf(\"ugnot\") \u003e= minExecuteFee.Amount\n\t)\n\n\t// Check if the non-DAO member can cover the execute fee\n\tif !s.membStore.IsMember(caller) \u0026\u0026 !canCoverFee {\n\t\treturn ErrInsufficientExecuteFunds\n\t}\n\n\t// Check if the proposal exists\n\tpropRaw, err := s.ProposalByID(id)\n\tif err != nil {\n\t\treturn ufmt.Errorf(\"unable to get proposal %d, %s\", id, err.Error())\n\t}\n\n\tprop := propRaw.(*proposal)\n\n\t// Check if the proposal is executed\n\tif prop.Status() == dao.ExecutionSuccessful ||\n\t\tprop.Status() == dao.ExecutionFailed {\n\t\t// Proposal is already executed\n\t\treturn ErrProposalExecuted\n\t}\n\n\t// Check the proposal status\n\tif prop.Status() != dao.Accepted {\n\t\t// Proposal is not accepted, cannot be executed\n\t\treturn ErrProposalNotAccepted\n\t}\n\n\t// Emit an event when the execution finishes\n\tdefer dao.EmitProposalExecuted(id, prop.status)\n\n\t// Attempt to execute the proposal\n\tif err = prop.executor.Execute(); err != nil {\n\t\tprop.status = dao.ExecutionFailed\n\n\t\treturn ufmt.Errorf(\"error during proposal %d execution, %s\", id, err.Error())\n\t}\n\n\t// Update the proposal status\n\tprop.status = dao.ExecutionSuccessful\n\n\treturn nil\n}\n\n// getDAOCaller returns the DAO caller.\n// XXX: This is not a great way to determine the caller, and it is very unsafe.\n// However, the current MsgRun context does not persist escaping the main() scope.\n// Until a better solution is developed, this enables proposals to be made through a package deployment + init()\nfunc getDAOCaller() std.Address {\n\treturn std.GetOrigCaller()\n}\n"},{"name":"dao_test.gno","body":"package simpledao\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\n// generateMembers generates dummy govdao members\nfunc generateMembers(t *testing.T, count int) []membstore.Member {\n\tt.Helper()\n\n\tmembers := make([]membstore.Member, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tmembers = append(members, membstore.Member{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"member %d\", i)),\n\t\t\tVotingPower: 10,\n\t\t})\n\t}\n\n\treturn members\n}\n\nfunc TestSimpleDAO_Propose(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"invalid executor\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\ts := New(nil)\n\n\t\t_, err := s.Propose(dao.ProposalRequest{})\n\t\tuassert.ErrorIs(\n\t\t\tt,\n\t\t\terr,\n\t\t\tErrInvalidExecutor,\n\t\t)\n\t})\n\n\tt.Run(\"invalid title\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\t\t\tcb = func() error {\n\t\t\t\tcalled = true\n\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tex = \u0026mockExecutor{\n\t\t\t\texecuteFn: cb,\n\t\t\t}\n\n\t\t\tsentCoins = std.NewCoins(\n\t\t\t\tstd.NewCoin(\n\t\t\t\t\t\"ugnot\",\n\t\t\t\t\tminProposalFeeValue,\n\t\t\t\t),\n\t\t\t)\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tisMemberFn: func(_ std.Address) bool {\n\t\t\t\t\treturn false\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\t\t)\n\n\t\tstd.TestSetOrigSend(sentCoins, std.Coins{})\n\n\t\t_, err := s.Propose(dao.ProposalRequest{\n\t\t\tExecutor: ex,\n\t\t\tTitle: \"\", // Set invalid title\n\t\t})\n\t\tuassert.ErrorIs(\n\t\t\tt,\n\t\t\terr,\n\t\t\tErrInvalidTitle,\n\t\t)\n\n\t\tuassert.False(t, called)\n\t})\n\n\tt.Run(\"caller cannot cover fee\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\t\t\tcb = func() error {\n\t\t\t\tcalled = true\n\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tex = \u0026mockExecutor{\n\t\t\t\texecuteFn: cb,\n\t\t\t}\n\t\t\ttitle = \"Proposal title\"\n\n\t\t\tsentCoins = std.NewCoins(\n\t\t\t\tstd.NewCoin(\n\t\t\t\t\t\"ugnot\",\n\t\t\t\t\tminProposalFeeValue-1,\n\t\t\t\t),\n\t\t\t)\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tisMemberFn: func(_ std.Address) bool {\n\t\t\t\t\treturn false\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\t\t)\n\n\t\t// Set the sent coins to be lower\n\t\t// than the proposal fee\n\t\tstd.TestSetOrigSend(sentCoins, std.Coins{})\n\n\t\t_, err := s.Propose(dao.ProposalRequest{\n\t\t\tExecutor: ex,\n\t\t\tTitle: title,\n\t\t})\n\t\tuassert.ErrorIs(\n\t\t\tt,\n\t\t\terr,\n\t\t\tErrInsufficientProposalFunds,\n\t\t)\n\n\t\tuassert.False(t, called)\n\t})\n\n\tt.Run(\"proposal added\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\t\t\tcb = func() error {\n\t\t\t\tcalled = true\n\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tex = \u0026mockExecutor{\n\t\t\t\texecuteFn: cb,\n\t\t\t}\n\t\t\tdescription = \"Proposal description\"\n\t\t\ttitle = \"Proposal title\"\n\n\t\t\tproposer = testutils.TestAddress(\"proposer\")\n\t\t\tsentCoins = std.NewCoins(\n\t\t\t\tstd.NewCoin(\n\t\t\t\t\t\"ugnot\",\n\t\t\t\t\tminProposalFeeValue, // enough to cover\n\t\t\t\t),\n\t\t\t)\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tisMemberFn: func(addr std.Address) bool {\n\t\t\t\t\treturn addr == proposer\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\t\t)\n\n\t\t// Set the sent coins to be enough\n\t\t// to cover the fee\n\t\tstd.TestSetOrigSend(sentCoins, std.Coins{})\n\t\tstd.TestSetOrigCaller(proposer)\n\n\t\t// Make sure the proposal was added\n\t\tid, err := s.Propose(dao.ProposalRequest{\n\t\t\tTitle: title,\n\t\t\tDescription: description,\n\t\t\tExecutor: ex,\n\t\t})\n\t\tuassert.NoError(t, err)\n\t\tuassert.False(t, called)\n\n\t\t// Make sure the proposal exists\n\t\tprop, err := s.ProposalByID(id)\n\t\tuassert.NoError(t, err)\n\n\t\tuassert.Equal(t, proposer.String(), prop.Author().String())\n\t\tuassert.Equal(t, description, prop.Description())\n\t\tuassert.Equal(t, title, prop.Title())\n\t\tuassert.Equal(t, dao.Active.String(), prop.Status().String())\n\n\t\tstats := prop.Stats()\n\n\t\tuassert.Equal(t, uint64(0), stats.YayVotes)\n\t\tuassert.Equal(t, uint64(0), stats.NayVotes)\n\t\tuassert.Equal(t, uint64(0), stats.AbstainVotes)\n\t\tuassert.Equal(t, uint64(0), stats.TotalVotingPower)\n\t})\n}\n\nfunc TestSimpleDAO_VoteOnProposal(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"not govdao member\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tvoter = testutils.TestAddress(\"voter\")\n\t\t\tfetchErr = errors.New(\"fetch error\")\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tmemberFn: func(_ std.Address) (membstore.Member, error) {\n\t\t\t\t\treturn membstore.Member{\n\t\t\t\t\t\tAddress: voter,\n\t\t\t\t\t}, fetchErr\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\t\t)\n\n\t\tstd.TestSetOrigCaller(voter)\n\n\t\t// Attempt to vote on the proposal\n\t\tuassert.ErrorContains(\n\t\t\tt,\n\t\t\ts.VoteOnProposal(0, dao.YesVote),\n\t\t\tfetchErr.Error(),\n\t\t)\n\t})\n\n\tt.Run(\"missing proposal\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tvoter = testutils.TestAddress(\"voter\")\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tmemberFn: func(a std.Address) (membstore.Member, error) {\n\t\t\t\t\tif a != voter {\n\t\t\t\t\t\treturn membstore.Member{}, errors.New(\"not found\")\n\t\t\t\t\t}\n\n\t\t\t\t\treturn membstore.Member{\n\t\t\t\t\t\tAddress: voter,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t}\n\n\t\t\ts = New(ms)\n\t\t)\n\n\t\tstd.TestSetOrigCaller(voter)\n\n\t\t// Attempt to vote on the proposal\n\t\tuassert.ErrorContains(\n\t\t\tt,\n\t\t\ts.VoteOnProposal(0, dao.YesVote),\n\t\t\tErrMissingProposal.Error(),\n\t\t)\n\t})\n\n\tt.Run(\"proposal executed\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tvoter = testutils.TestAddress(\"voter\")\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tmemberFn: func(a std.Address) (membstore.Member, error) {\n\t\t\t\t\tif a != voter {\n\t\t\t\t\t\treturn membstore.Member{}, errors.New(\"not found\")\n\t\t\t\t\t}\n\n\t\t\t\t\treturn membstore.Member{\n\t\t\t\t\t\tAddress: voter,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\n\t\t\tprop = \u0026proposal{\n\t\t\t\tstatus: dao.ExecutionSuccessful,\n\t\t\t}\n\t\t)\n\n\t\tstd.TestSetOrigCaller(voter)\n\n\t\t// Add an initial proposal\n\t\tid, err := s.addProposal(prop)\n\t\turequire.NoError(t, err)\n\n\t\t// Attempt to vote on the proposal\n\t\tuassert.ErrorIs(\n\t\t\tt,\n\t\t\ts.VoteOnProposal(id, dao.YesVote),\n\t\t\tErrProposalInactive,\n\t\t)\n\t})\n\n\tt.Run(\"double vote on proposal\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tvoter = testutils.TestAddress(\"voter\")\n\t\t\tmember = membstore.Member{\n\t\t\t\tAddress: voter,\n\t\t\t\tVotingPower: 10,\n\t\t\t}\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tmemberFn: func(a std.Address) (membstore.Member, error) {\n\t\t\t\t\tif a != voter {\n\t\t\t\t\t\treturn membstore.Member{}, errors.New(\"not found\")\n\t\t\t\t\t}\n\n\t\t\t\t\treturn member, nil\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\n\t\t\tprop = \u0026proposal{\n\t\t\t\tstatus: dao.Active,\n\t\t\t\texecutor: \u0026mockExecutor{},\n\t\t\t\ttally: newTally(),\n\t\t\t}\n\t\t)\n\n\t\tstd.TestSetOrigCaller(voter)\n\n\t\t// Cast the initial vote\n\t\turequire.NoError(t, prop.tally.castVote(member, dao.YesVote))\n\n\t\t// Add an initial proposal\n\t\tid, err := s.addProposal(prop)\n\t\turequire.NoError(t, err)\n\n\t\t// Attempt to vote on the proposal\n\t\tuassert.ErrorContains(\n\t\t\tt,\n\t\t\ts.VoteOnProposal(id, dao.YesVote),\n\t\t\tErrAlreadyVoted.Error(),\n\t\t)\n\t})\n\n\tt.Run(\"majority accepted\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tmembers = generateMembers(t, 50)\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tmemberFn: func(address std.Address) (membstore.Member, error) {\n\t\t\t\t\tfor _, m := range members {\n\t\t\t\t\t\tif m.Address == address {\n\t\t\t\t\t\t\treturn m, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn membstore.Member{}, errors.New(\"not found\")\n\t\t\t\t},\n\n\t\t\t\ttotalPowerFn: func() uint64 {\n\t\t\t\t\tpower := uint64(0)\n\n\t\t\t\t\tfor _, m := range members {\n\t\t\t\t\t\tpower += m.VotingPower\n\t\t\t\t\t}\n\n\t\t\t\t\treturn power\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\n\t\t\tprop = \u0026proposal{\n\t\t\t\tstatus: dao.Active,\n\t\t\t\texecutor: \u0026mockExecutor{},\n\t\t\t\ttally: newTally(),\n\t\t\t}\n\t\t)\n\n\t\t// Add an initial proposal\n\t\tid, err := s.addProposal(prop)\n\t\turequire.NoError(t, err)\n\n\t\tmajorityIndex := (len(members)*2)/3 + 1 // 2/3+\n\t\tfor _, m := range members[:majorityIndex] {\n\t\t\tstd.TestSetOrigCaller(m.Address)\n\n\t\t\t// Attempt to vote on the proposal\n\t\t\turequire.NoError(\n\t\t\t\tt,\n\t\t\t\ts.VoteOnProposal(id, dao.YesVote),\n\t\t\t)\n\t\t}\n\n\t\t// Make sure the proposal was accepted\n\t\tuassert.Equal(t, dao.Accepted.String(), prop.status.String())\n\t})\n\n\tt.Run(\"majority rejected\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tmembers = generateMembers(t, 50)\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tmemberFn: func(address std.Address) (membstore.Member, error) {\n\t\t\t\t\tfor _, m := range members {\n\t\t\t\t\t\tif m.Address == address {\n\t\t\t\t\t\t\treturn m, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn membstore.Member{}, errors.New(\"member not found\")\n\t\t\t\t},\n\n\t\t\t\ttotalPowerFn: func() uint64 {\n\t\t\t\t\tpower := uint64(0)\n\n\t\t\t\t\tfor _, m := range members {\n\t\t\t\t\t\tpower += m.VotingPower\n\t\t\t\t\t}\n\n\t\t\t\t\treturn power\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\n\t\t\tprop = \u0026proposal{\n\t\t\t\tstatus: dao.Active,\n\t\t\t\texecutor: \u0026mockExecutor{},\n\t\t\t\ttally: newTally(),\n\t\t\t}\n\t\t)\n\n\t\t// Add an initial proposal\n\t\tid, err := s.addProposal(prop)\n\t\turequire.NoError(t, err)\n\n\t\tmajorityIndex := (len(members)*2)/3 + 1 // 2/3+\n\t\tfor _, m := range members[:majorityIndex] {\n\t\t\tstd.TestSetOrigCaller(m.Address)\n\n\t\t\t// Attempt to vote on the proposal\n\t\t\turequire.NoError(\n\t\t\t\tt,\n\t\t\t\ts.VoteOnProposal(id, dao.NoVote),\n\t\t\t)\n\t\t}\n\n\t\t// Make sure the proposal was not accepted\n\t\tuassert.Equal(t, dao.NotAccepted.String(), prop.status.String())\n\t})\n\n\tt.Run(\"majority abstained\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tmembers = generateMembers(t, 50)\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tmemberFn: func(address std.Address) (membstore.Member, error) {\n\t\t\t\t\tfor _, m := range members {\n\t\t\t\t\t\tif m.Address == address {\n\t\t\t\t\t\t\treturn m, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn membstore.Member{}, errors.New(\"member not found\")\n\t\t\t\t},\n\n\t\t\t\ttotalPowerFn: func() uint64 {\n\t\t\t\t\tpower := uint64(0)\n\n\t\t\t\t\tfor _, m := range members {\n\t\t\t\t\t\tpower += m.VotingPower\n\t\t\t\t\t}\n\n\t\t\t\t\treturn power\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\n\t\t\tprop = \u0026proposal{\n\t\t\t\tstatus: dao.Active,\n\t\t\t\texecutor: \u0026mockExecutor{},\n\t\t\t\ttally: newTally(),\n\t\t\t}\n\t\t)\n\n\t\t// Add an initial proposal\n\t\tid, err := s.addProposal(prop)\n\t\turequire.NoError(t, err)\n\n\t\tmajorityIndex := (len(members)*2)/3 + 1 // 2/3+\n\t\tfor _, m := range members[:majorityIndex] {\n\t\t\tstd.TestSetOrigCaller(m.Address)\n\n\t\t\t// Attempt to vote on the proposal\n\t\t\turequire.NoError(\n\t\t\t\tt,\n\t\t\t\ts.VoteOnProposal(id, dao.AbstainVote),\n\t\t\t)\n\t\t}\n\n\t\t// Make sure the proposal was not accepted\n\t\tuassert.Equal(t, dao.NotAccepted.String(), prop.status.String())\n\t})\n\n\tt.Run(\"everyone voted, undecided\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tmembers = generateMembers(t, 50)\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tmemberFn: func(address std.Address) (membstore.Member, error) {\n\t\t\t\t\tfor _, m := range members {\n\t\t\t\t\t\tif m.Address == address {\n\t\t\t\t\t\t\treturn m, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn membstore.Member{}, errors.New(\"member not found\")\n\t\t\t\t},\n\n\t\t\t\ttotalPowerFn: func() uint64 {\n\t\t\t\t\tpower := uint64(0)\n\n\t\t\t\t\tfor _, m := range members {\n\t\t\t\t\t\tpower += m.VotingPower\n\t\t\t\t\t}\n\n\t\t\t\t\treturn power\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\n\t\t\tprop = \u0026proposal{\n\t\t\t\tstatus: dao.Active,\n\t\t\t\texecutor: \u0026mockExecutor{},\n\t\t\t\ttally: newTally(),\n\t\t\t}\n\t\t)\n\n\t\t// Add an initial proposal\n\t\tid, err := s.addProposal(prop)\n\t\turequire.NoError(t, err)\n\n\t\t// The first half votes yes\n\t\tfor _, m := range members[:len(members)/2] {\n\t\t\tstd.TestSetOrigCaller(m.Address)\n\n\t\t\t// Attempt to vote on the proposal\n\t\t\turequire.NoError(\n\t\t\t\tt,\n\t\t\t\ts.VoteOnProposal(id, dao.YesVote),\n\t\t\t)\n\t\t}\n\n\t\t// The other half votes no\n\t\tfor _, m := range members[len(members)/2:] {\n\t\t\tstd.TestSetOrigCaller(m.Address)\n\n\t\t\t// Attempt to vote on the proposal\n\t\t\turequire.NoError(\n\t\t\t\tt,\n\t\t\t\ts.VoteOnProposal(id, dao.NoVote),\n\t\t\t)\n\t\t}\n\n\t\t// Make sure the proposal is not active,\n\t\t// since everyone voted, and it was undecided\n\t\tuassert.Equal(t, dao.NotAccepted.String(), prop.status.String())\n\t})\n\n\tt.Run(\"proposal undecided\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tmembers = generateMembers(t, 50)\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tmemberFn: func(address std.Address) (membstore.Member, error) {\n\t\t\t\t\tfor _, m := range members {\n\t\t\t\t\t\tif m.Address == address {\n\t\t\t\t\t\t\treturn m, nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn membstore.Member{}, errors.New(\"member not found\")\n\t\t\t\t},\n\n\t\t\t\ttotalPowerFn: func() uint64 {\n\t\t\t\t\tpower := uint64(0)\n\n\t\t\t\t\tfor _, m := range members {\n\t\t\t\t\t\tpower += m.VotingPower\n\t\t\t\t\t}\n\n\t\t\t\t\treturn power\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\n\t\t\tprop = \u0026proposal{\n\t\t\t\tstatus: dao.Active,\n\t\t\t\texecutor: \u0026mockExecutor{},\n\t\t\t\ttally: newTally(),\n\t\t\t}\n\t\t)\n\n\t\t// Add an initial proposal\n\t\tid, err := s.addProposal(prop)\n\t\turequire.NoError(t, err)\n\n\t\t// The first quarter votes yes\n\t\tfor _, m := range members[:len(members)/4] {\n\t\t\tstd.TestSetOrigCaller(m.Address)\n\n\t\t\t// Attempt to vote on the proposal\n\t\t\turequire.NoError(\n\t\t\t\tt,\n\t\t\t\ts.VoteOnProposal(id, dao.YesVote),\n\t\t\t)\n\t\t}\n\n\t\t// The second quarter votes no\n\t\tfor _, m := range members[len(members)/4 : len(members)/2] {\n\t\t\tstd.TestSetOrigCaller(m.Address)\n\n\t\t\t// Attempt to vote on the proposal\n\t\t\turequire.NoError(\n\t\t\t\tt,\n\t\t\t\ts.VoteOnProposal(id, dao.NoVote),\n\t\t\t)\n\t\t}\n\n\t\t// Make sure the proposal is still active,\n\t\t// since there wasn't quorum reached on any decision\n\t\tuassert.Equal(t, dao.Active.String(), prop.status.String())\n\t})\n}\n\nfunc TestSimpleDAO_ExecuteProposal(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"caller cannot cover fee\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tsentCoins = std.NewCoins(\n\t\t\t\tstd.NewCoin(\n\t\t\t\t\t\"ugnot\",\n\t\t\t\t\tminExecuteFeeValue-1,\n\t\t\t\t),\n\t\t\t)\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tisMemberFn: func(_ std.Address) bool {\n\t\t\t\t\treturn false\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\t\t)\n\n\t\t// Set the sent coins to be lower\n\t\t// than the execute fee\n\t\tstd.TestSetOrigSend(sentCoins, std.Coins{})\n\n\t\tuassert.ErrorIs(\n\t\t\tt,\n\t\t\ts.ExecuteProposal(0),\n\t\t\tErrInsufficientExecuteFunds,\n\t\t)\n\t})\n\n\tt.Run(\"missing proposal\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tsentCoins = std.NewCoins(\n\t\t\t\tstd.NewCoin(\n\t\t\t\t\t\"ugnot\",\n\t\t\t\t\tminExecuteFeeValue,\n\t\t\t\t),\n\t\t\t)\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tisMemberFn: func(_ std.Address) bool {\n\t\t\t\t\treturn true\n\t\t\t\t},\n\t\t\t}\n\n\t\t\ts = New(ms)\n\t\t)\n\n\t\t// Set the sent coins to be enough\n\t\t// so the execution can take place\n\t\tstd.TestSetOrigSend(sentCoins, std.Coins{})\n\n\t\tuassert.ErrorContains(\n\t\t\tt,\n\t\t\ts.ExecuteProposal(0),\n\t\t\tErrMissingProposal.Error(),\n\t\t)\n\t})\n\n\tt.Run(\"proposal not accepted\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tvoter = testutils.TestAddress(\"voter\")\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tisMemberFn: func(_ std.Address) bool {\n\t\t\t\t\treturn true\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\n\t\t\tprop = \u0026proposal{\n\t\t\t\tstatus: dao.NotAccepted,\n\t\t\t}\n\t\t)\n\n\t\tstd.TestSetOrigCaller(voter)\n\n\t\t// Add an initial proposal\n\t\tid, err := s.addProposal(prop)\n\t\turequire.NoError(t, err)\n\n\t\t// Attempt to vote on the proposal\n\t\tuassert.ErrorIs(\n\t\t\tt,\n\t\t\ts.ExecuteProposal(id),\n\t\t\tErrProposalNotAccepted,\n\t\t)\n\t})\n\n\tt.Run(\"proposal already executed\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\ttestTable := []struct {\n\t\t\tname string\n\t\t\tstatus dao.ProposalStatus\n\t\t}{\n\t\t\t{\n\t\t\t\t\"execution was successful\",\n\t\t\t\tdao.ExecutionSuccessful,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"execution failed\",\n\t\t\t\tdao.ExecutionFailed,\n\t\t\t},\n\t\t}\n\n\t\tfor _, testCase := range testTable {\n\t\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\t\tt.Parallel()\n\n\t\t\t\tvar (\n\t\t\t\t\tvoter = testutils.TestAddress(\"voter\")\n\n\t\t\t\t\tms = \u0026mockMemberStore{\n\t\t\t\t\t\tisMemberFn: func(_ std.Address) bool {\n\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t\ts = New(ms)\n\n\t\t\t\t\tprop = \u0026proposal{\n\t\t\t\t\t\tstatus: testCase.status,\n\t\t\t\t\t}\n\t\t\t\t)\n\n\t\t\t\tstd.TestSetOrigCaller(voter)\n\n\t\t\t\t// Add an initial proposal\n\t\t\t\tid, err := s.addProposal(prop)\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\t// Attempt to vote on the proposal\n\t\t\t\tuassert.ErrorIs(\n\t\t\t\t\tt,\n\t\t\t\t\ts.ExecuteProposal(id),\n\t\t\t\t\tErrProposalExecuted,\n\t\t\t\t)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"execution error\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tvoter = testutils.TestAddress(\"voter\")\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tisMemberFn: func(_ std.Address) bool {\n\t\t\t\t\treturn true\n\t\t\t\t},\n\t\t\t}\n\n\t\t\ts = New(ms)\n\n\t\t\texecError = errors.New(\"exec error\")\n\n\t\t\tmockExecutor = \u0026mockExecutor{\n\t\t\t\texecuteFn: func() error {\n\t\t\t\t\treturn execError\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tprop = \u0026proposal{\n\t\t\t\tstatus: dao.Accepted,\n\t\t\t\texecutor: mockExecutor,\n\t\t\t}\n\t\t)\n\n\t\tstd.TestSetOrigCaller(voter)\n\n\t\t// Add an initial proposal\n\t\tid, err := s.addProposal(prop)\n\t\turequire.NoError(t, err)\n\n\t\t// Attempt to vote on the proposal\n\t\tuassert.ErrorContains(\n\t\t\tt,\n\t\t\ts.ExecuteProposal(id),\n\t\t\texecError.Error(),\n\t\t)\n\n\t\tuassert.Equal(t, dao.ExecutionFailed.String(), prop.status.String())\n\t})\n\n\tt.Run(\"successful execution\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tvoter = testutils.TestAddress(\"voter\")\n\n\t\t\tms = \u0026mockMemberStore{\n\t\t\t\tisMemberFn: func(_ std.Address) bool {\n\t\t\t\t\treturn true\n\t\t\t\t},\n\t\t\t}\n\t\t\ts = New(ms)\n\n\t\t\tcalled = false\n\t\t\tmockExecutor = \u0026mockExecutor{\n\t\t\t\texecuteFn: func() error {\n\t\t\t\t\tcalled = true\n\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tprop = \u0026proposal{\n\t\t\t\tstatus: dao.Accepted,\n\t\t\t\texecutor: mockExecutor,\n\t\t\t}\n\t\t)\n\n\t\tstd.TestSetOrigCaller(voter)\n\n\t\t// Add an initial proposal\n\t\tid, err := s.addProposal(prop)\n\t\turequire.NoError(t, err)\n\n\t\t// Attempt to vote on the proposal\n\t\tuassert.NoError(t, s.ExecuteProposal(id))\n\t\tuassert.Equal(t, dao.ExecutionSuccessful.String(), prop.status.String())\n\t\tuassert.True(t, called)\n\t})\n}\n"},{"name":"mock_test.gno","body":"package simpledao\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/membstore\"\n)\n\ntype executeDelegate func() error\n\ntype mockExecutor struct {\n\texecuteFn executeDelegate\n}\n\nfunc (m *mockExecutor) Execute() error {\n\tif m.executeFn != nil {\n\t\treturn m.executeFn()\n\t}\n\n\treturn nil\n}\n\ntype (\n\tmembersDelegate func(uint64, uint64) []membstore.Member\n\tsizeDelegate func() int\n\tisMemberDelegate func(std.Address) bool\n\ttotalPowerDelegate func() uint64\n\tmemberDelegate func(std.Address) (membstore.Member, error)\n\taddMemberDelegate func(membstore.Member) error\n\tupdateMemberDelegate func(std.Address, membstore.Member) error\n)\n\ntype mockMemberStore struct {\n\tmembersFn membersDelegate\n\tsizeFn sizeDelegate\n\tisMemberFn isMemberDelegate\n\ttotalPowerFn totalPowerDelegate\n\tmemberFn memberDelegate\n\taddMemberFn addMemberDelegate\n\tupdateMemberFn updateMemberDelegate\n}\n\nfunc (m *mockMemberStore) Members(offset, count uint64) []membstore.Member {\n\tif m.membersFn != nil {\n\t\treturn m.membersFn(offset, count)\n\t}\n\n\treturn nil\n}\n\nfunc (m *mockMemberStore) Size() int {\n\tif m.sizeFn != nil {\n\t\treturn m.sizeFn()\n\t}\n\n\treturn 0\n}\n\nfunc (m *mockMemberStore) IsMember(address std.Address) bool {\n\tif m.isMemberFn != nil {\n\t\treturn m.isMemberFn(address)\n\t}\n\n\treturn false\n}\n\nfunc (m *mockMemberStore) TotalPower() uint64 {\n\tif m.totalPowerFn != nil {\n\t\treturn m.totalPowerFn()\n\t}\n\n\treturn 0\n}\n\nfunc (m *mockMemberStore) Member(address std.Address) (membstore.Member, error) {\n\tif m.memberFn != nil {\n\t\treturn m.memberFn(address)\n\t}\n\n\treturn membstore.Member{}, nil\n}\n\nfunc (m *mockMemberStore) AddMember(member membstore.Member) error {\n\tif m.addMemberFn != nil {\n\t\treturn m.addMemberFn(member)\n\t}\n\n\treturn nil\n}\n\nfunc (m *mockMemberStore) UpdateMember(address std.Address, member membstore.Member) error {\n\tif m.updateMemberFn != nil {\n\t\treturn m.updateMemberFn(address, member)\n\t}\n\n\treturn nil\n}\n"},{"name":"propstore.gno","body":"package simpledao\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar ErrMissingProposal = errors.New(\"proposal is missing\")\n\n// maxRequestProposals is the maximum number of\n// paginated proposals that can be requested\nconst maxRequestProposals = 10\n\n// proposal is the internal simpledao proposal implementation\ntype proposal struct {\n\tauthor std.Address // initiator of the proposal\n\ttitle string // title of the proposal\n\tdescription string // description of the proposal\n\n\texecutor dao.Executor // executor for the proposal\n\tstatus dao.ProposalStatus // status of the proposal\n\n\ttally *tally // voting tally\n\tgetTotalVotingPowerFn func() uint64 // callback for the total voting power\n}\n\nfunc (p *proposal) Author() std.Address {\n\treturn p.author\n}\n\nfunc (p *proposal) Title() string {\n\treturn p.title\n}\n\nfunc (p *proposal) Description() string {\n\treturn p.description\n}\n\nfunc (p *proposal) Status() dao.ProposalStatus {\n\treturn p.status\n}\n\nfunc (p *proposal) Executor() dao.Executor {\n\treturn p.executor\n}\n\nfunc (p *proposal) Stats() dao.Stats {\n\t// Get the total voting power of the body\n\ttotalPower := p.getTotalVotingPowerFn()\n\n\treturn dao.Stats{\n\t\tYayVotes: p.tally.yays,\n\t\tNayVotes: p.tally.nays,\n\t\tAbstainVotes: p.tally.abstains,\n\t\tTotalVotingPower: totalPower,\n\t}\n}\n\nfunc (p *proposal) IsExpired() bool {\n\treturn false // this proposal never expires\n}\n\nfunc (p *proposal) Render() string {\n\t// Fetch the voting stats\n\tstats := p.Stats()\n\n\tvar out string\n\n\tout += \"## Description\\n\\n\"\n\tif strings.TrimSpace(p.description) != \"\" {\n\t\tout += ufmt.Sprintf(\"%s\\n\\n\", p.description)\n\t} else {\n\t\tout += \"No description provided.\\n\\n\"\n\t}\n\n\tout += \"## Proposal information\\n\\n\"\n\tout += ufmt.Sprintf(\"**Status: %s**\\n\\n\", strings.ToUpper(p.Status().String()))\n\n\tout += ufmt.Sprintf(\n\t\t\"**Voting stats:**\\n- YES %d (%d%%)\\n- NO %d (%d%%)\\n- ABSTAIN %d (%d%%)\\n- MISSING VOTES %d (%d%%)\\n\",\n\t\tstats.YayVotes,\n\t\tstats.YayPercent(),\n\t\tstats.NayVotes,\n\t\tstats.NayPercent(),\n\t\tstats.AbstainVotes,\n\t\tstats.AbstainPercent(),\n\t\tstats.MissingVotes(),\n\t\tstats.MissingVotesPercent(),\n\t)\n\n\tout += \"\\n\\n\"\n\tthresholdOut := strings.ToUpper(ufmt.Sprintf(\"%t\", stats.YayVotes \u003e (2*stats.TotalVotingPower)/3))\n\n\tout += ufmt.Sprintf(\"**Threshold met: %s**\\n\\n\", thresholdOut)\n\n\treturn out\n}\n\n// addProposal adds a new simpledao proposal to the store\nfunc (s *SimpleDAO) addProposal(proposal *proposal) (uint64, error) {\n\t// See what the next proposal number should be\n\tnextID := uint64(s.proposals.Size())\n\n\t// Save the proposal\n\ts.proposals.Set(getProposalID(nextID), proposal)\n\n\treturn nextID, nil\n}\n\nfunc (s *SimpleDAO) Proposals(offset, count uint64) []dao.Proposal {\n\t// Check the requested count\n\tif count \u003c 1 {\n\t\treturn []dao.Proposal{}\n\t}\n\n\t// Limit the maximum number of returned proposals\n\tif count \u003e maxRequestProposals {\n\t\tcount = maxRequestProposals\n\t}\n\n\tvar (\n\t\tstartIndex = offset\n\t\tendIndex = startIndex + count\n\n\t\tnumProposals = uint64(s.proposals.Size())\n\t)\n\n\t// Check if the current offset has any proposals\n\tif startIndex \u003e= numProposals {\n\t\treturn []dao.Proposal{}\n\t}\n\n\t// Check if the right bound is good\n\tif endIndex \u003e numProposals {\n\t\tendIndex = numProposals\n\t}\n\n\tprops := make([]dao.Proposal, 0)\n\ts.proposals.Iterate(\n\t\tgetProposalID(startIndex),\n\t\tgetProposalID(endIndex),\n\t\tfunc(_ string, val interface{}) bool {\n\t\t\tprop := val.(*proposal)\n\n\t\t\t// Save the proposal\n\t\t\tprops = append(props, prop)\n\n\t\t\treturn false\n\t\t},\n\t)\n\n\treturn props\n}\n\nfunc (s *SimpleDAO) ProposalByID(id uint64) (dao.Proposal, error) {\n\tprop, exists := s.proposals.Get(getProposalID(id))\n\tif !exists {\n\t\treturn nil, ErrMissingProposal\n\t}\n\n\treturn prop.(*proposal), nil\n}\n\nfunc (s *SimpleDAO) Size() int {\n\treturn s.proposals.Size()\n}\n\n// getProposalID generates a sequential proposal ID\n// from the given ID number\nfunc getProposalID(id uint64) string {\n\treturn seqid.ID(id).String()\n}\n"},{"name":"propstore_test.gno","body":"package simpledao\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\n// generateProposals generates dummy proposals\nfunc generateProposals(t *testing.T, count int) []*proposal {\n\tt.Helper()\n\n\tvar (\n\t\tmembers = generateMembers(t, count)\n\t\tproposals = make([]*proposal, 0, count)\n\t)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tproposal := \u0026proposal{\n\t\t\tauthor: members[i].Address,\n\t\t\tdescription: ufmt.Sprintf(\"proposal %d\", i),\n\t\t\tstatus: dao.Active,\n\t\t\ttally: newTally(),\n\t\t\tgetTotalVotingPowerFn: func() uint64 {\n\t\t\t\treturn 0\n\t\t\t},\n\t\t\texecutor: nil,\n\t\t}\n\n\t\tproposals = append(proposals, proposal)\n\t}\n\n\treturn proposals\n}\n\nfunc equalProposals(t *testing.T, p1, p2 dao.Proposal) {\n\tt.Helper()\n\n\tuassert.Equal(\n\t\tt,\n\t\tp1.Author().String(),\n\t\tp2.Author().String(),\n\t)\n\n\tuassert.Equal(\n\t\tt,\n\t\tp1.Description(),\n\t\tp2.Description(),\n\t)\n\n\tuassert.Equal(\n\t\tt,\n\t\tp1.Status().String(),\n\t\tp2.Status().String(),\n\t)\n\n\tp1Stats := p1.Stats()\n\tp2Stats := p2.Stats()\n\n\tuassert.Equal(t, p1Stats.YayVotes, p2Stats.YayVotes)\n\tuassert.Equal(t, p1Stats.NayVotes, p2Stats.NayVotes)\n\tuassert.Equal(t, p1Stats.AbstainVotes, p2Stats.AbstainVotes)\n\tuassert.Equal(t, p1Stats.TotalVotingPower, p2Stats.TotalVotingPower)\n}\n\nfunc TestProposal_Data(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"author\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tp := \u0026proposal{\n\t\t\tauthor: testutils.TestAddress(\"address\"),\n\t\t}\n\n\t\tuassert.Equal(t, p.author, p.Author())\n\t})\n\n\tt.Run(\"description\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tp := \u0026proposal{\n\t\t\tdescription: \"example proposal description\",\n\t\t}\n\n\t\tuassert.Equal(t, p.description, p.Description())\n\t})\n\n\tt.Run(\"status\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tp := \u0026proposal{\n\t\t\tstatus: dao.ExecutionSuccessful,\n\t\t}\n\n\t\tuassert.Equal(t, p.status.String(), p.Status().String())\n\t})\n\n\tt.Run(\"executor\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tnumCalled = 0\n\t\t\tcb = func() error {\n\t\t\t\tnumCalled++\n\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tex = \u0026mockExecutor{\n\t\t\t\texecuteFn: cb,\n\t\t\t}\n\n\t\t\tp = \u0026proposal{\n\t\t\t\texecutor: ex,\n\t\t\t}\n\t\t)\n\n\t\turequire.NoError(t, p.executor.Execute())\n\t\turequire.NoError(t, p.Executor().Execute())\n\n\t\tuassert.Equal(t, 2, numCalled)\n\t})\n\n\tt.Run(\"no votes\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tp := \u0026proposal{\n\t\t\ttally: newTally(),\n\t\t\tgetTotalVotingPowerFn: func() uint64 {\n\t\t\t\treturn 0\n\t\t\t},\n\t\t}\n\n\t\tstats := p.Stats()\n\n\t\tuassert.Equal(t, uint64(0), stats.YayVotes)\n\t\tuassert.Equal(t, uint64(0), stats.NayVotes)\n\t\tuassert.Equal(t, uint64(0), stats.AbstainVotes)\n\t\tuassert.Equal(t, uint64(0), stats.TotalVotingPower)\n\t})\n\n\tt.Run(\"existing votes\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tmembers = generateMembers(t, 50)\n\t\t\ttotalPower = uint64(len(members)) * 10\n\n\t\t\tp = \u0026proposal{\n\t\t\t\ttally: newTally(),\n\t\t\t\tgetTotalVotingPowerFn: func() uint64 {\n\t\t\t\t\treturn totalPower\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\n\t\tfor _, m := range members {\n\t\t\turequire.NoError(t, p.tally.castVote(m, dao.YesVote))\n\t\t}\n\n\t\tstats := p.Stats()\n\n\t\tuassert.Equal(t, totalPower, stats.YayVotes)\n\t\tuassert.Equal(t, uint64(0), stats.NayVotes)\n\t\tuassert.Equal(t, uint64(0), stats.AbstainVotes)\n\t\tuassert.Equal(t, totalPower, stats.TotalVotingPower)\n\t})\n}\n\nfunc TestSimpleDAO_GetProposals(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"no proposals\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\ts := New(nil)\n\n\t\tuassert.Equal(t, 0, s.Size())\n\t\tproposals := s.Proposals(0, 0)\n\n\t\tuassert.Equal(t, 0, len(proposals))\n\t})\n\n\tt.Run(\"proper pagination\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tnumProposals = 20\n\t\t\thalfRange = numProposals / 2\n\n\t\t\ts = New(nil)\n\t\t\tproposals = generateProposals(t, numProposals)\n\t\t)\n\n\t\t// Add initial proposals\n\t\tfor _, proposal := range proposals {\n\t\t\t_, err := s.addProposal(proposal)\n\n\t\t\turequire.NoError(t, err)\n\t\t}\n\n\t\tuassert.Equal(t, numProposals, s.Size())\n\n\t\tfetchedProposals := s.Proposals(0, uint64(halfRange))\n\t\turequire.Equal(t, halfRange, len(fetchedProposals))\n\n\t\tfor index, fetchedProposal := range fetchedProposals {\n\t\t\tequalProposals(t, proposals[index], fetchedProposal)\n\t\t}\n\n\t\t// Fetch the other half\n\t\tfetchedProposals = s.Proposals(uint64(halfRange), uint64(halfRange))\n\t\turequire.Equal(t, halfRange, len(fetchedProposals))\n\n\t\tfor index, fetchedProposal := range fetchedProposals {\n\t\t\tequalProposals(t, proposals[index+halfRange], fetchedProposal)\n\t\t}\n\t})\n}\n\nfunc TestSimpleDAO_GetProposalByID(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"missing proposal\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\ts := New(nil)\n\n\t\t_, err := s.ProposalByID(0)\n\t\tuassert.ErrorIs(t, err, ErrMissingProposal)\n\t})\n\n\tt.Run(\"proposal found\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\ts = New(nil)\n\t\t\tproposal = generateProposals(t, 1)[0]\n\t\t)\n\n\t\t// Add the initial proposal\n\t\t_, err := s.addProposal(proposal)\n\t\turequire.NoError(t, err)\n\n\t\t// Fetch the proposal\n\t\tfetchedProposal, err := s.ProposalByID(0)\n\t\turequire.NoError(t, err)\n\n\t\tequalProposals(t, proposal, fetchedProposal)\n\t})\n}\n"},{"name":"votestore.gno","body":"package simpledao\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n)\n\nvar ErrAlreadyVoted = errors.New(\"vote already cast\")\n\n// tally is a simple vote tally system\ntype tally struct {\n\t// tally cache to keep track of active\n\t// yes / no / abstain votes\n\tyays uint64\n\tnays uint64\n\tabstains uint64\n\n\tvoters *avl.Tree // std.Address -\u003e dao.VoteOption\n}\n\n// newTally creates a new tally system instance\nfunc newTally() *tally {\n\treturn \u0026tally{\n\t\tvoters: avl.NewTree(),\n\t}\n}\n\n// castVote casts a single vote in the name of the given member\nfunc (t *tally) castVote(member membstore.Member, option dao.VoteOption) error {\n\t// Check if the member voted already\n\taddress := member.Address.String()\n\n\t_, voted := t.voters.Get(address)\n\tif voted {\n\t\treturn ErrAlreadyVoted\n\t}\n\n\t// convert option to upper-case, like the constants are.\n\toption = dao.VoteOption(strings.ToUpper(string(option)))\n\n\t// Update the tally\n\tswitch option {\n\tcase dao.YesVote:\n\t\tt.yays += member.VotingPower\n\tcase dao.AbstainVote:\n\t\tt.abstains += member.VotingPower\n\tcase dao.NoVote:\n\t\tt.nays += member.VotingPower\n\tdefault:\n\t\tpanic(\"invalid voting option: \" + option)\n\t}\n\n\t// Save the voting status\n\tt.voters.Set(address, option)\n\n\treturn nil\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} {"tx":{"msg":[{"@type":"/vm.m_call","caller":"g1juz2yxmdsa6audkp6ep9vfv80c8p5u76e03vvh","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateReply","args":["2","1","1","Hello at 2024-11-29 09:35 UTC"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A8vtky0+3ckLTmOsJA4Ti2X2aE5rZp0TrtMmXCX/vwIg"},"signature":"pYkwQfxeaNVhyrRd/7Hy+T0SzUMMSKHscQiV73vefeJtJH0rzjaECQmdZDGqav9vDThRoiaZZfXT0BWFqubbqA=="}],"memo":""},"metadata":{"timestamp":"1732872944638"}} {"tx":{"msg":[{"@type":"/bank.MsgSend","from_address":"g1qhuef2450xh7g7na8s865nreu2xw8j84kgkvt5","to_address":"g150gdvn780ws7a3s9qpdvv7h4d9hq6vh672ely0","amount":"10000000ugnot"}],"fee":{"gas_wanted":"100000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AwEKz9MRdU4MCAzB3el9G4wLJJQeWiRYtRquzEgm19Z2"},"signature":"iYL/vym7Z8UUCNvwgmyFptcrkeSny+kYjr/pMvavUPk7+1iPjhjAAJrtkAzEuLg1hxHO3JW+L+r6tV5bDi76Cw=="}],"memo":""},"metadata":{"timestamp":"1733147614520"}} +{"tx":{"msg":[{"@type":"/bank.MsgSend","from_address":"g1qhuef2450xh7g7na8s865nreu2xw8j84kgkvt5","to_address":"g150gdvn780ws7a3s9qpdvv7h4d9hq6vh672ely0","amount":"10000000ugnot"}],"fee":{"gas_wanted":"100000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AwEKz9MRdU4MCAzB3el9G4wLJJQeWiRYtRquzEgm19Z2"},"signature":"iYL/vym7Z8UUCNvwgmyFptcrkeSny+kYjr/pMvavUPk7+1iPjhjAAJrtkAzEuLg1hxHO3JW+L+r6tV5bDi76Cw=="}],"memo":""},"metadata":{"timestamp":"1733224189204"}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"bar20","path":"gno.land/r/demo/bar20","files":[{"name":"bar20.gno","body":"// Package bar20 is similar to gno.land/r/demo/foo20 but exposes a safe-object\n// that can be used by `maketx run`, another contract importing foo20, and in\n// the future when we'll support `maketx call Token.XXX`.\npackage bar20\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/grc/grc20\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/grc20reg\"\n)\n\nvar (\n\tToken, adm = grc20.NewToken(\"Bar\", \"BAR\", 4)\n\tUserTeller = Token.CallerTeller()\n)\n\nfunc init() {\n\tgetter := func() *grc20.Token { return Token }\n\tgrc20reg.Register(getter, \"\")\n}\n\nfunc Faucet() string {\n\tcaller := std.PrevRealm().Addr()\n\tif err := adm.Mint(caller, 1_000_000); err != nil {\n\t\treturn \"error: \" + err.Error()\n\t}\n\treturn \"OK\"\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\tc := len(parts)\n\n\tswitch {\n\tcase path == \"\":\n\t\treturn Token.RenderHome()\n\tcase c == 2 \u0026\u0026 parts[0] == \"balance\":\n\t\towner := std.Address(parts[1])\n\t\tbalance := Token.BalanceOf(owner)\n\t\treturn ufmt.Sprintf(\"%d\\n\", balance)\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n"},{"name":"bar20_test.gno","body":"package bar20\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestPackage(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice) // XXX: should not need this\n\n\turequire.Equal(t, UserTeller.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, UserTeller.BalanceOf(alice), uint64(1_000_000))\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"crossrealm","path":"gno.land/r/demo/tests/crossrealm","files":[{"name":"crossrealm.gno","body":"package crossrealm\n\nimport (\n\t\"gno.land/p/demo/tests/p_crossrealm\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype LocalStruct struct {\n\tA int\n}\n\nfunc (ls *LocalStruct) String() string {\n\treturn ufmt.Sprintf(\"LocalStruct{%d}\", ls.A)\n}\n\n// local is saved locally in this realm\nvar local *LocalStruct\n\nfunc init() {\n\tlocal = \u0026LocalStruct{A: 123}\n}\n\n// Make1 returns a local object wrapped by a p struct\nfunc Make1() *p_crossrealm.Container {\n\treturn \u0026p_crossrealm.Container{\n\t\tA: 1,\n\t\tB: local,\n\t}\n}\n\ntype Fooer interface{ Foo() }\n\nvar fooer Fooer\n\nfunc SetFooer(f Fooer) Fooer {\n\tfooer = f\n\treturn fooer\n}\n\nfunc GetFooer() Fooer { return fooer }\n\nfunc CallFooerFoo() { fooer.Foo() }\n\ntype FooerGetter func() Fooer\n\nvar fooerGetter FooerGetter\n\nfunc SetFooerGetter(fg FooerGetter) FooerGetter {\n\tfooerGetter = fg\n\treturn fg\n}\n\nfunc GetFooerGetter() FooerGetter {\n\treturn fooerGetter\n}\n\nfunc CallFooerGetterFoo() { fooerGetter().Foo() }\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"crossrealm_b","path":"gno.land/r/demo/tests/crossrealm_b","files":[{"name":"crossrealm.gno","body":"package crossrealm_b\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/tests/crossrealm\"\n)\n\ntype fooer struct {\n\ts string\n}\n\nfunc (f *fooer) SetS(newVal string) {\n\tf.s = newVal\n}\n\nfunc (f *fooer) Foo() {\n\tprintln(\"hello \" + f.s + \" cur=\" + std.CurrentRealm().PkgPath() + \" prev=\" + std.PrevRealm().PkgPath())\n}\n\nvar (\n\tFooer = \u0026fooer{s: \"A\"}\n\tFooerGetter = func() crossrealm.Fooer { return Fooer }\n\tFooerGetterBuilder = func() crossrealm.FooerGetter { return func() crossrealm.Fooer { return Fooer } }\n)\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"foo20","path":"gno.land/r/demo/foo20","files":[{"name":"foo20.gno","body":"// foo20 is a GRC20 token contract where all the grc20.Teller methods are\n// proxified with top-level functions. see also gno.land/r/demo/bar20.\npackage foo20\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/grc/grc20\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/demo/grc20reg\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tToken, privateLedger = grc20.NewToken(\"Foo\", \"FOO\", 4)\n\tUserTeller = Token.CallerTeller()\n\towner = ownable.NewWithAddress(\"g1manfred47kzduec920z88wfr64ylksmdcedlf5\") // @manfred\n)\n\nfunc init() {\n\tprivateLedger.Mint(owner.Owner(), 1_000_000*10_000) // @privateLedgeristrator (1M)\n\tgetter := func() *grc20.Token { return Token }\n\tgrc20reg.Register(getter, \"\")\n}\n\nfunc TotalSupply() uint64 {\n\treturn UserTeller.TotalSupply()\n}\n\nfunc BalanceOf(owner pusers.AddressOrName) uint64 {\n\townerAddr := users.Resolve(owner)\n\treturn UserTeller.BalanceOf(ownerAddr)\n}\n\nfunc Allowance(owner, spender pusers.AddressOrName) uint64 {\n\townerAddr := users.Resolve(owner)\n\tspenderAddr := users.Resolve(spender)\n\treturn UserTeller.Allowance(ownerAddr, spenderAddr)\n}\n\nfunc Transfer(to pusers.AddressOrName, amount uint64) {\n\ttoAddr := users.Resolve(to)\n\tcheckErr(UserTeller.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(UserTeller.Approve(spenderAddr, amount))\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, amount uint64) {\n\tfromAddr := users.Resolve(from)\n\ttoAddr := users.Resolve(to)\n\tcheckErr(UserTeller.TransferFrom(fromAddr, toAddr, amount))\n}\n\n// Faucet is distributing foo20 tokens without restriction (unsafe).\n// For a real token faucet, you should take care of setting limits are asking payment.\nfunc Faucet() {\n\tcaller := std.PrevRealm().Addr()\n\tamount := uint64(1_000 * 10_000) // 1k\n\tcheckErr(privateLedger.Mint(caller, amount))\n}\n\nfunc Mint(to pusers.AddressOrName, amount uint64) {\n\towner.AssertCallerIsOwner()\n\ttoAddr := users.Resolve(to)\n\tcheckErr(privateLedger.Mint(toAddr, amount))\n}\n\nfunc Burn(from pusers.AddressOrName, amount uint64) {\n\towner.AssertCallerIsOwner()\n\tfromAddr := users.Resolve(from)\n\tcheckErr(privateLedger.Burn(fromAddr, amount))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\tc := len(parts)\n\n\tswitch {\n\tcase path == \"\":\n\t\treturn Token.RenderHome()\n\tcase c == 2 \u0026\u0026 parts[0] == \"balance\":\n\t\towner := pusers.AddressOrName(parts[1])\n\t\townerAddr := users.Resolve(owner)\n\t\tbalance := UserTeller.BalanceOf(ownerAddr)\n\t\treturn ufmt.Sprintf(\"%d\\n\", balance)\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"foo20_test.gno","body":"package foo20\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tvar (\n\t\tadmin = pusers.AddressOrName(\"g1manfred47kzduec920z88wfr64ylksmdcedlf5\")\n\t\talice = pusers.AddressOrName(testutils.TestAddress(\"alice\"))\n\t\tbob = pusers.AddressOrName(testutils.TestAddress(\"bob\"))\n\t)\n\n\ttype test struct {\n\t\tname string\n\t\tbalance uint64\n\t\tfn func() uint64\n\t}\n\n\t// check balances #1.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_000_000_000, func() uint64 { return TotalSupply() }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(admin) }},\n\t\t\t{\"BalanceOf(alice)\", 0, func() uint64 { return BalanceOf(alice) }},\n\t\t\t{\"Allowance(admin, alice)\", 0, func() uint64 { return Allowance(admin, alice) }},\n\t\t\t{\"BalanceOf(bob)\", 0, func() uint64 { return BalanceOf(bob) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tgot := tc.fn()\n\t\t\tuassert.Equal(t, got, tc.balance)\n\t\t}\n\t}\n\n\t// bob uses the faucet.\n\tstd.TestSetOrigCaller(users.Resolve(bob))\n\tFaucet()\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_010_000_000, func() uint64 { return TotalSupply() }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(admin) }},\n\t\t\t{\"BalanceOf(alice)\", 0, func() uint64 { return BalanceOf(alice) }},\n\t\t\t{\"Allowance(admin, alice)\", 0, func() uint64 { return Allowance(admin, alice) }},\n\t\t\t{\"BalanceOf(bob)\", 10_000_000, func() uint64 { return BalanceOf(bob) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tgot := tc.fn()\n\t\t\tuassert.Equal(t, got, tc.balance)\n\t\t}\n\t}\n}\n\nfunc TestErrConditions(t *testing.T) {\n\tvar (\n\t\tadmin = pusers.AddressOrName(\"g1manfred47kzduec920z88wfr64ylksmdcedlf5\")\n\t\talice = pusers.AddressOrName(testutils.TestAddress(\"alice\"))\n\t\tempty = pusers.AddressOrName(\"\")\n\t)\n\n\ttype test struct {\n\t\tname string\n\t\tmsg string\n\t\tfn func()\n\t}\n\n\tprivateLedger.Mint(std.Address(admin), 10000)\n\t{\n\t\ttests := []test{\n\t\t\t{\"Transfer(admin, 1)\", \"cannot send transfer to self\", func() {\n\t\t\t\t// XXX: should replace with: Transfer(admin, 1)\n\t\t\t\t// but there is currently a limitation in manipulating the frame stack and simulate\n\t\t\t\t// calling this package from an outside point of view.\n\t\t\t\tadminAddr := std.Address(admin)\n\t\t\t\tif err := privateLedger.Transfer(adminAddr, adminAddr, 1); err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t}},\n\t\t\t{\"Approve(empty, 1))\", \"invalid address\", func() { Approve(empty, 1) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tuassert.PanicsWithMessage(t, tc.msg, tc.fn)\n\t\t\t})\n\t\t}\n\t}\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"foo20","path":"gno.land/r/demo/grc20factory","files":[{"name":"grc20factory.gno","body":"package foo20\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/grc/grc20\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/grc20reg\"\n)\n\nvar instances avl.Tree // symbol -\u003e instance\n\ntype instance struct {\n\ttoken *grc20.Token\n\tledger *grc20.PrivateLedger\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc New(name, symbol string, decimals uint, initialMint, faucet uint64) {\n\tcaller := std.PrevRealm().Addr()\n\tNewWithAdmin(name, symbol, decimals, initialMint, faucet, caller)\n}\n\nfunc NewWithAdmin(name, symbol string, decimals uint, initialMint, faucet uint64, admin std.Address) {\n\texists := instances.Has(symbol)\n\tif exists {\n\t\tpanic(\"token already exists\")\n\t}\n\n\ttoken, ledger := grc20.NewToken(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tledger.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\ttoken: token,\n\t\tledger: ledger,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\tinstances.Set(symbol, \u0026inst)\n\tgetter := func() *grc20.Token { return token }\n\tgrc20reg.Register(getter, symbol)\n}\n\nfunc (inst instance) Token() *grc20.Token {\n\treturn inst.token\n}\n\nfunc (inst instance) CallerTeller() grc20.Teller {\n\treturn inst.token.CallerTeller()\n}\n\nfunc Bank(symbol string) *grc20.Token {\n\tinst := mustGetInstance(symbol)\n\treturn inst.token\n}\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.token.ReadonlyTeller().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.token.ReadonlyTeller().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.token.ReadonlyTeller().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcaller := std.PrevRealm().Addr()\n\tteller := inst.ledger.ImpersonateTeller(caller)\n\tcheckErr(teller.Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcaller := std.PrevRealm().Addr()\n\tteller := inst.ledger.ImpersonateTeller(caller)\n\tcheckErr(teller.Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcaller := std.PrevRealm().Addr()\n\tteller := inst.ledger.ImpersonateTeller(caller)\n\tcheckErr(teller.TransferFrom(from, to, amount))\n}\n\n// faucet.\nfunc Faucet(symbol string) {\n\tinst := mustGetInstance(symbol)\n\tif inst.faucet == 0 {\n\t\tpanic(\"faucet disabled for this token\")\n\t}\n\t// FIXME: add limits?\n\t// FIXME: add payment in gnot?\n\tcaller := std.PrevRealm().Addr()\n\tcheckErr(inst.ledger.Mint(caller, inst.faucet))\n}\n\nfunc Mint(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tinst.admin.AssertCallerIsOwner()\n\tcheckErr(inst.ledger.Mint(to, amount))\n}\n\nfunc Burn(symbol string, from std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tinst.admin.AssertCallerIsOwner()\n\tcheckErr(inst.ledger.Burn(from, amount))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\tc := len(parts)\n\n\tswitch {\n\tcase path == \"\":\n\t\treturn \"TODO: list existing tokens and admins\"\n\tcase c == 1:\n\t\tsymbol := parts[0]\n\t\tinst := mustGetInstance(symbol)\n\t\treturn inst.token.RenderHome()\n\tcase c == 3 \u0026\u0026 parts[1] == \"balance\":\n\t\tsymbol := parts[0]\n\t\tinst := mustGetInstance(symbol)\n\t\towner := std.Address(parts[2])\n\t\tbalance := inst.token.CallerTeller().BalanceOf(owner)\n\t\treturn ufmt.Sprintf(\"%d\", balance)\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\nfunc mustGetInstance(symbol string) *instance {\n\tt, exists := instances.Get(symbol)\n\tif !exists {\n\t\tpanic(\"token instance does not exist\")\n\t}\n\treturn t.(*instance)\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n}\n"},{"name":"grc20factory_test.gno","body":"package foo20\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tstd.TestSetOrigPkgAddr(\"gno.land/r/demo/grc20factory\")\n\tadmin := testutils.TestAddress(\"admin\")\n\tbob := testutils.TestAddress(\"bob\")\n\tcarl := testutils.TestAddress(\"carl\")\n\n\ttype test struct {\n\t\tname string\n\t\tbalance uint64\n\t\tfn func() uint64\n\t}\n\n\tcheckBalances := func(step string, totSup, balAdm, balBob, allowAdmBob, balCarl uint64) {\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", totSup, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", balAdm, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(bob)\", balBob, func() uint64 { return BalanceOf(\"FOO\", bob) }},\n\t\t\t{\"Allowance(admin, bob)\", allowAdmBob, func() uint64 { return Allowance(\"FOO\", admin, bob) }},\n\t\t\t{\"BalanceOf(carl)\", balCarl, func() uint64 { return BalanceOf(\"FOO\", carl) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\treason := ufmt.Sprintf(\"%s.%s - %s\", step, tc.name, \"balances do not match\")\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), reason)\n\t\t}\n\t}\n\n\t// admin creates FOO and BAR.\n\tstd.TestSetOrigCaller(admin)\n\tstd.TestSetRealm(std.NewUserRealm(admin))\n\tNewWithAdmin(\"Foo\", \"FOO\", 3, 1_111_111_000, 5_555, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 3, 2_222_000, 6_666, admin)\n\tcheckBalances(\"step1\", 1_111_111_000, 1_111_111_000, 0, 0, 0)\n\n\t// admin mints to bob.\n\tmustGetInstance(\"FOO\").ledger.Mint(bob, 333_333_000)\n\tcheckBalances(\"step2\", 1_444_444_000, 1_111_111_000, 333_333_000, 0, 0)\n\n\t// carl uses the faucet.\n\tstd.TestSetOrigCaller(carl)\n\tstd.TestSetRealm(std.NewUserRealm(carl))\n\tFaucet(\"FOO\")\n\tcheckBalances(\"step3\", 1_444_449_555, 1_111_111_000, 333_333_000, 0, 5_555)\n\n\t// admin gives to bob some allowance.\n\tstd.TestSetOrigCaller(admin)\n\tstd.TestSetRealm(std.NewUserRealm(admin))\n\tApprove(\"FOO\", bob, 1_000_000)\n\tcheckBalances(\"step4\", 1_444_449_555, 1_111_111_000, 333_333_000, 1_000_000, 5_555)\n\n\t// bob uses a part of the allowance.\n\tstd.TestSetOrigCaller(bob)\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tTransferFrom(\"FOO\", admin, carl, 400_000)\n\tcheckBalances(\"step5\", 1_444_449_555, 1_110_711_000, 333_333_000, 600_000, 405_555)\n\n\t// bob uses a part of the allowance.\n\tstd.TestSetOrigCaller(bob)\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tTransferFrom(\"FOO\", admin, carl, 600_000)\n\tcheckBalances(\"step6\", 1_444_449_555, 1_110_111_000, 333_333_000, 0, 1_005_555)\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"grc20","path":"gno.land/p/demo/grc/grc20","files":[{"name":"examples_test.gno","body":"package grc20\n\n// XXX: write Examples\n\nfunc ExampleInit() {}\nfunc ExampleExposeBankForMaketxRunOrImports() {}\nfunc ExampleCustomTellerImpl() {}\nfunc ExampleAllowance() {}\nfunc ExampleRealmBanker() {}\nfunc ExamplePrevRealmBanker() {}\nfunc ExampleAccountBanker() {}\nfunc ExampleTransfer() {}\nfunc ExampleApprove() {}\nfunc ExampleTransferFrom() {}\nfunc ExampleMint() {}\nfunc ExampleBurn() {}\n\n// ...\n"},{"name":"mock.gno","body":"package grc20\n\n// XXX: func Mock(t *Token)\n"},{"name":"tellers.gno","body":"package grc20\n\nimport (\n\t\"std\"\n)\n\n// CallerTeller returns a GRC20 compatible teller that checks the PrevRealm\n// caller for each call. It's usually safe to expose it publicly to let users\n// manipulate their tokens directly, or for realms to use their allowance.\nfunc (tok *Token) CallerTeller() Teller {\n\tif tok == nil {\n\t\tpanic(\"Token cannot be nil\")\n\t}\n\n\treturn \u0026fnTeller{\n\t\taccountFn: func() std.Address {\n\t\t\tcaller := std.PrevRealm().Addr()\n\t\t\treturn caller\n\t\t},\n\t\tToken: tok,\n\t}\n}\n\n// ReadonlyTeller is a GRC20 compatible teller that panics for any write operation.\nfunc (tok *Token) ReadonlyTeller() Teller {\n\tif tok == nil {\n\t\tpanic(\"Token cannot be nil\")\n\t}\n\n\treturn \u0026fnTeller{\n\t\taccountFn: nil,\n\t\tToken: tok,\n\t}\n}\n\n// RealmTeller returns a GRC20 compatible teller that will store the\n// caller realm permanently. Calling anything through this teller will\n// result in allowance or balance changes for the realm that initialized the teller.\n// The initializer of this teller should usually never share the resulting Teller from\n// this method except maybe for advanced delegation flows such as a DAO treasury\n// management.\nfunc (tok *Token) RealmTeller() Teller {\n\tif tok == nil {\n\t\tpanic(\"Token cannot be nil\")\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\n\treturn \u0026fnTeller{\n\t\taccountFn: func() std.Address {\n\t\t\treturn caller\n\t\t},\n\t\tToken: tok,\n\t}\n}\n\n// RealmSubTeller is like RealmTeller but uses the provided slug to derive a\n// subaccount.\nfunc (tok *Token) RealmSubTeller(slug string) Teller {\n\tif tok == nil {\n\t\tpanic(\"Token cannot be nil\")\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\taccount := accountSlugAddr(caller, slug)\n\n\treturn \u0026fnTeller{\n\t\taccountFn: func() std.Address {\n\t\t\treturn account\n\t\t},\n\t\tToken: tok,\n\t}\n}\n\n// ImpersonateTeller returns a GRC20 compatible teller that impersonates as a\n// specified address. This allows operations to be performed as if they were\n// executed by the given address, enabling the caller to manipulate tokens on\n// behalf of that address.\n//\n// It is particularly useful in scenarios where a contract needs to perform\n// actions on behalf of a user or another account, without exposing the\n// underlying logic or requiring direct access to the user's account. The\n// returned teller will use the provided address for all operations, effectively\n// masking the original caller.\n//\n// This method should be used with caution, as it allows for potentially\n// sensitive operations to be performed under the guise of another address.\nfunc (ledger *PrivateLedger) ImpersonateTeller(addr std.Address) Teller {\n\tif ledger == nil {\n\t\tpanic(\"Ledger cannot be nil\")\n\t}\n\n\treturn \u0026fnTeller{\n\t\taccountFn: func() std.Address {\n\t\t\treturn addr\n\t\t},\n\t\tToken: ledger.token,\n\t}\n}\n\n// generic tellers methods.\n//\n\nfunc (ft *fnTeller) Transfer(to std.Address, amount uint64) error {\n\tif ft.accountFn == nil {\n\t\treturn ErrReadonly\n\t}\n\tcaller := ft.accountFn()\n\treturn ft.Token.ledger.Transfer(caller, to, amount)\n}\n\nfunc (ft *fnTeller) Approve(spender std.Address, amount uint64) error {\n\tif ft.accountFn == nil {\n\t\treturn ErrReadonly\n\t}\n\tcaller := ft.accountFn()\n\treturn ft.Token.ledger.Approve(caller, spender, amount)\n}\n\nfunc (ft *fnTeller) TransferFrom(owner, to std.Address, amount uint64) error {\n\tif ft.accountFn == nil {\n\t\treturn ErrReadonly\n\t}\n\tspender := ft.accountFn()\n\treturn ft.Token.ledger.TransferFrom(owner, spender, to, amount)\n}\n\n// helpers\n//\n\n// accountSlugAddr returns the address derived from the specified address and slug.\nfunc accountSlugAddr(addr std.Address, slug string) std.Address {\n\t// XXX: use a new `std.XXX` call for this.\n\tif slug == \"\" {\n\t\treturn addr\n\t}\n\tkey := addr.String() + \"/\" + slug\n\treturn std.DerivePkgAddr(key) // temporarily using this helper\n}\n"},{"name":"tellers_test.gno","body":"package grc20\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestCallerTellerImpl(t *testing.T) {\n\ttok, _ := NewToken(\"Dummy\", \"DUMMY\", 4)\n\tteller := tok.CallerTeller()\n\turequire.False(t, tok == nil)\n\tvar _ Teller = teller\n}\n\nfunc TestTeller(t *testing.T) {\n\tvar (\n\t\talice = testutils.TestAddress(\"alice\")\n\t\tbob = testutils.TestAddress(\"bob\")\n\t\tcarl = testutils.TestAddress(\"carl\")\n\t)\n\n\ttoken, ledger := NewToken(\"Dummy\", \"DUMMY\", 6)\n\n\tcheckBalances := func(aliceEB, bobEB, carlEB uint64) {\n\t\tt.Helper()\n\t\texp := ufmt.Sprintf(\"alice=%d bob=%d carl=%d\", aliceEB, bobEB, carlEB)\n\t\taliceGB := token.BalanceOf(alice)\n\t\tbobGB := token.BalanceOf(bob)\n\t\tcarlGB := token.BalanceOf(carl)\n\t\tgot := ufmt.Sprintf(\"alice=%d bob=%d carl=%d\", aliceGB, bobGB, carlGB)\n\t\tuassert.Equal(t, got, exp, \"invalid balances\")\n\t}\n\tcheckAllowances := func(abEB, acEB, baEB, bcEB, caEB, cbEB uint64) {\n\t\tt.Helper()\n\t\texp := ufmt.Sprintf(\"ab=%d ac=%d ba=%d bc=%d ca=%d cb=%s\", abEB, acEB, baEB, bcEB, caEB, cbEB)\n\t\tabGB := token.Allowance(alice, bob)\n\t\tacGB := token.Allowance(alice, carl)\n\t\tbaGB := token.Allowance(bob, alice)\n\t\tbcGB := token.Allowance(bob, carl)\n\t\tcaGB := token.Allowance(carl, alice)\n\t\tcbGB := token.Allowance(carl, bob)\n\t\tgot := ufmt.Sprintf(\"ab=%d ac=%d ba=%d bc=%d ca=%d cb=%s\", abGB, acGB, baGB, bcGB, caGB, cbGB)\n\t\tuassert.Equal(t, got, exp, \"invalid allowances\")\n\t}\n\n\tcheckBalances(0, 0, 0)\n\tcheckAllowances(0, 0, 0, 0, 0, 0)\n\n\turequire.NoError(t, ledger.Mint(alice, 1000))\n\turequire.NoError(t, ledger.Mint(alice, 100))\n\tcheckBalances(1100, 0, 0)\n\tcheckAllowances(0, 0, 0, 0, 0, 0)\n\n\turequire.NoError(t, ledger.Approve(alice, bob, 99999999))\n\tcheckBalances(1100, 0, 0)\n\tcheckAllowances(99999999, 0, 0, 0, 0, 0)\n\n\turequire.NoError(t, ledger.Approve(alice, bob, 400))\n\tcheckBalances(1100, 0, 0)\n\tcheckAllowances(400, 0, 0, 0, 0, 0)\n\n\turequire.Error(t, ledger.TransferFrom(alice, bob, carl, 100000000))\n\tcheckBalances(1100, 0, 0)\n\tcheckAllowances(400, 0, 0, 0, 0, 0)\n\n\turequire.NoError(t, ledger.TransferFrom(alice, bob, carl, 100))\n\tcheckBalances(1000, 0, 100)\n\tcheckAllowances(300, 0, 0, 0, 0, 0)\n\n\turequire.Error(t, ledger.SpendAllowance(alice, bob, 2000000))\n\tcheckBalances(1000, 0, 100)\n\tcheckAllowances(300, 0, 0, 0, 0, 0)\n\n\turequire.NoError(t, ledger.SpendAllowance(alice, bob, 100))\n\tcheckBalances(1000, 0, 100)\n\tcheckAllowances(200, 0, 0, 0, 0, 0)\n}\n\nfunc TestCallerTeller(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tbob := testutils.TestAddress(\"bob\")\n\tcarl := testutils.TestAddress(\"carl\")\n\n\ttoken, ledger := NewToken(\"Dummy\", \"DUMMY\", 6)\n\tteller := token.CallerTeller()\n\n\tcheckBalances := func(aliceEB, bobEB, carlEB uint64) {\n\t\tt.Helper()\n\t\texp := ufmt.Sprintf(\"alice=%d bob=%d carl=%d\", aliceEB, bobEB, carlEB)\n\t\taliceGB := token.BalanceOf(alice)\n\t\tbobGB := token.BalanceOf(bob)\n\t\tcarlGB := token.BalanceOf(carl)\n\t\tgot := ufmt.Sprintf(\"alice=%d bob=%d carl=%d\", aliceGB, bobGB, carlGB)\n\t\tuassert.Equal(t, got, exp, \"invalid balances\")\n\t}\n\tcheckAllowances := func(abEB, acEB, baEB, bcEB, caEB, cbEB uint64) {\n\t\tt.Helper()\n\t\texp := ufmt.Sprintf(\"ab=%d ac=%d ba=%d bc=%d ca=%d cb=%s\", abEB, acEB, baEB, bcEB, caEB, cbEB)\n\t\tabGB := token.Allowance(alice, bob)\n\t\tacGB := token.Allowance(alice, carl)\n\t\tbaGB := token.Allowance(bob, alice)\n\t\tbcGB := token.Allowance(bob, carl)\n\t\tcaGB := token.Allowance(carl, alice)\n\t\tcbGB := token.Allowance(carl, bob)\n\t\tgot := ufmt.Sprintf(\"ab=%d ac=%d ba=%d bc=%d ca=%d cb=%s\", abGB, acGB, baGB, bcGB, caGB, cbGB)\n\t\tuassert.Equal(t, got, exp, \"invalid allowances\")\n\t}\n\n\turequire.NoError(t, ledger.Mint(alice, 1000))\n\tcheckBalances(1000, 0, 0)\n\tcheckAllowances(0, 0, 0, 0, 0, 0)\n\n\tstd.TestSetOrigCaller(alice)\n\turequire.NoError(t, teller.Approve(bob, 600))\n\tcheckBalances(1000, 0, 0)\n\tcheckAllowances(600, 0, 0, 0, 0, 0)\n\n\tstd.TestSetOrigCaller(bob)\n\turequire.Error(t, teller.TransferFrom(alice, carl, 700))\n\tcheckBalances(1000, 0, 0)\n\tcheckAllowances(600, 0, 0, 0, 0, 0)\n\turequire.NoError(t, teller.TransferFrom(alice, carl, 400))\n\tcheckBalances(600, 0, 400)\n\tcheckAllowances(200, 0, 0, 0, 0, 0)\n}\n"},{"name":"token.gno","body":"package grc20\n\nimport (\n\t\"math/overflow\"\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// NewToken creates a new Token.\n// It returns a pointer to the Token and a pointer to the Ledger.\n// Expected usage: Token, admin := NewToken(\"Dummy\", \"DUMMY\", 4)\nfunc NewToken(name, symbol string, decimals uint) (*Token, *PrivateLedger) {\n\tif name == \"\" {\n\t\tpanic(\"name should not be empty\")\n\t}\n\tif symbol == \"\" {\n\t\tpanic(\"symbol should not be empty\")\n\t}\n\t// XXX additional checks (length, characters, limits, etc)\n\n\tledger := \u0026PrivateLedger{}\n\ttoken := \u0026Token{\n\t\tname: name,\n\t\tsymbol: symbol,\n\t\tdecimals: decimals,\n\t\tledger: ledger,\n\t}\n\tledger.token = token\n\treturn token, ledger\n}\n\n// GetName returns the name of the token.\nfunc (tok Token) GetName() string { return tok.name }\n\n// GetSymbol returns the symbol of the token.\nfunc (tok Token) GetSymbol() string { return tok.symbol }\n\n// GetDecimals returns the number of decimals used to get the token's precision.\nfunc (tok Token) GetDecimals() uint { return tok.decimals }\n\n// TotalSupply returns the total supply of the token.\nfunc (tok Token) TotalSupply() uint64 { return tok.ledger.totalSupply }\n\n// KnownAccounts returns the number of known accounts in the bank.\nfunc (tok Token) KnownAccounts() int { return tok.ledger.balances.Size() }\n\n// BalanceOf returns the balance of the specified address.\nfunc (tok Token) BalanceOf(address std.Address) uint64 {\n\treturn tok.ledger.balanceOf(address)\n}\n\n// Allowance returns the allowance of the specified owner and spender.\nfunc (tok Token) Allowance(owner, spender std.Address) uint64 {\n\treturn tok.ledger.allowance(owner, spender)\n}\n\nfunc (tok *Token) RenderHome() string {\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", tok.name, tok.symbol)\n\tstr += ufmt.Sprintf(\"* **Decimals**: %d\\n\", tok.decimals)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", tok.ledger.totalSupply)\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", tok.KnownAccounts())\n\treturn str\n}\n\n// SpendAllowance decreases the allowance of the specified owner and spender.\nfunc (led *PrivateLedger) SpendAllowance(owner, spender std.Address, amount uint64) error {\n\tif !owner.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\tif !spender.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcurrentAllowance := led.allowance(owner, spender)\n\tif currentAllowance \u003c amount {\n\t\treturn ErrInsufficientAllowance\n\t}\n\n\tkey := allowanceKey(owner, spender)\n\tnewAllowance := currentAllowance - amount\n\n\tif newAllowance == 0 {\n\t\tled.allowances.Remove(key)\n\t} else {\n\t\tled.allowances.Set(key, newAllowance)\n\t}\n\n\treturn nil\n}\n\n// Transfer transfers tokens from the specified from address to the specified to address.\nfunc (led *PrivateLedger) Transfer(from, to std.Address, amount uint64) error {\n\tif !from.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\tif !to.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\tvar (\n\t\ttoBalance = led.balanceOf(to)\n\t\tfromBalance = led.balanceOf(from)\n\t)\n\n\tif fromBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tvar (\n\t\tnewToBalance = toBalance + amount\n\t\tnewFromBalance = fromBalance - amount\n\t)\n\n\tled.balances.Set(string(to), newToBalance)\n\tled.balances.Set(string(from), newFromBalance)\n\n\tstd.Emit(\n\t\tTransferEvent,\n\t\t\"from\", from.String(),\n\t\t\"to\", to.String(),\n\t\t\"value\", strconv.Itoa(int(amount)),\n\t)\n\n\treturn nil\n}\n\n// TransferFrom transfers tokens from the specified owner to the specified to address.\n// It first checks if the owner has sufficient balance and then decreases the allowance.\nfunc (led *PrivateLedger) TransferFrom(owner, spender, to std.Address, amount uint64) error {\n\tif led.balanceOf(owner) \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\tif err := led.SpendAllowance(owner, spender, amount); err != nil {\n\t\treturn err\n\t}\n\t// XXX: since we don't \"panic\", we should take care of rollbacking spendAllowance if transfer fails.\n\treturn led.Transfer(owner, to, amount)\n}\n\n// Approve sets the allowance of the specified owner and spender.\nfunc (led *PrivateLedger) Approve(owner, spender std.Address, amount uint64) error {\n\tif !owner.IsValid() || !spender.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tled.allowances.Set(allowanceKey(owner, spender), amount)\n\n\tstd.Emit(\n\t\tApprovalEvent,\n\t\t\"owner\", string(owner),\n\t\t\"spender\", string(spender),\n\t\t\"value\", strconv.Itoa(int(amount)),\n\t)\n\n\treturn nil\n}\n\n// Mint increases the total supply of the token and adds the specified amount to the specified address.\nfunc (led *PrivateLedger) Mint(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\t// XXX: math/overflow is not supporting uint64.\n\t// This checks prevents overflow but makes the totalSupply limited to a uint63.\n\tsum, ok := overflow.Add64(int64(led.totalSupply), int64(amount))\n\tif !ok {\n\t\treturn ErrOverflow\n\t}\n\n\tled.totalSupply = uint64(sum)\n\tcurrentBalance := led.balanceOf(address)\n\tnewBalance := currentBalance + amount\n\n\tled.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tTransferEvent,\n\t\t\"from\", \"\",\n\t\t\"to\", string(address),\n\t\t\"value\", strconv.Itoa(int(amount)),\n\t)\n\n\treturn nil\n}\n\n// Burn decreases the total supply of the token and subtracts the specified amount from the specified address.\nfunc (led *PrivateLedger) Burn(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcurrentBalance := led.balanceOf(address)\n\tif currentBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tled.totalSupply -= amount\n\tnewBalance := currentBalance - amount\n\n\tled.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tTransferEvent,\n\t\t\"from\", string(address),\n\t\t\"to\", \"\",\n\t\t\"value\", strconv.Itoa(int(amount)),\n\t)\n\n\treturn nil\n}\n\n// balanceOf returns the balance of the specified address.\nfunc (led PrivateLedger) balanceOf(address std.Address) uint64 {\n\tbalance, found := led.balances.Get(address.String())\n\tif !found {\n\t\treturn 0\n\t}\n\treturn balance.(uint64)\n}\n\n// allowance returns the allowance of the specified owner and spender.\nfunc (led PrivateLedger) allowance(owner, spender std.Address) uint64 {\n\tallowance, found := led.allowances.Get(allowanceKey(owner, spender))\n\tif !found {\n\t\treturn 0\n\t}\n\treturn allowance.(uint64)\n}\n\n// allowanceKey returns the key for the allowance of the specified owner and spender.\nfunc allowanceKey(owner, spender std.Address) string {\n\treturn owner.String() + \":\" + spender.String()\n}\n"},{"name":"token_test.gno","body":"package grc20\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestTestImpl(t *testing.T) {\n\tbank, _ := NewToken(\"Dummy\", \"DUMMY\", 4)\n\turequire.False(t, bank == nil, \"dummy should not be nil\")\n}\n\nfunc TestToken(t *testing.T) {\n\tvar (\n\t\talice = testutils.TestAddress(\"alice\")\n\t\tbob = testutils.TestAddress(\"bob\")\n\t\tcarl = testutils.TestAddress(\"carl\")\n\t)\n\n\tbank, adm := NewToken(\"Dummy\", \"DUMMY\", 6)\n\n\tcheckBalances := func(aliceEB, bobEB, carlEB uint64) {\n\t\tt.Helper()\n\t\texp := ufmt.Sprintf(\"alice=%d bob=%d carl=%d\", aliceEB, bobEB, carlEB)\n\t\taliceGB := bank.BalanceOf(alice)\n\t\tbobGB := bank.BalanceOf(bob)\n\t\tcarlGB := bank.BalanceOf(carl)\n\t\tgot := ufmt.Sprintf(\"alice=%d bob=%d carl=%d\", aliceGB, bobGB, carlGB)\n\t\tuassert.Equal(t, got, exp, \"invalid balances\")\n\t}\n\tcheckAllowances := func(abEB, acEB, baEB, bcEB, caEB, cbEB uint64) {\n\t\tt.Helper()\n\t\texp := ufmt.Sprintf(\"ab=%d ac=%d ba=%d bc=%d ca=%d cb=%s\", abEB, acEB, baEB, bcEB, caEB, cbEB)\n\t\tabGB := bank.Allowance(alice, bob)\n\t\tacGB := bank.Allowance(alice, carl)\n\t\tbaGB := bank.Allowance(bob, alice)\n\t\tbcGB := bank.Allowance(bob, carl)\n\t\tcaGB := bank.Allowance(carl, alice)\n\t\tcbGB := bank.Allowance(carl, bob)\n\t\tgot := ufmt.Sprintf(\"ab=%d ac=%d ba=%d bc=%d ca=%d cb=%s\", abGB, acGB, baGB, bcGB, caGB, cbGB)\n\t\tuassert.Equal(t, got, exp, \"invalid allowances\")\n\t}\n\n\tcheckBalances(0, 0, 0)\n\tcheckAllowances(0, 0, 0, 0, 0, 0)\n\n\turequire.NoError(t, adm.Mint(alice, 1000))\n\turequire.NoError(t, adm.Mint(alice, 100))\n\tcheckBalances(1100, 0, 0)\n\tcheckAllowances(0, 0, 0, 0, 0, 0)\n\n\turequire.NoError(t, adm.Approve(alice, bob, 99999999))\n\tcheckBalances(1100, 0, 0)\n\tcheckAllowances(99999999, 0, 0, 0, 0, 0)\n\n\turequire.NoError(t, adm.Approve(alice, bob, 400))\n\tcheckBalances(1100, 0, 0)\n\tcheckAllowances(400, 0, 0, 0, 0, 0)\n\n\turequire.Error(t, adm.TransferFrom(alice, bob, carl, 100000000))\n\tcheckBalances(1100, 0, 0)\n\tcheckAllowances(400, 0, 0, 0, 0, 0)\n\n\turequire.NoError(t, adm.TransferFrom(alice, bob, carl, 100))\n\tcheckBalances(1000, 0, 100)\n\tcheckAllowances(300, 0, 0, 0, 0, 0)\n\n\turequire.Error(t, adm.SpendAllowance(alice, bob, 2000000))\n\tcheckBalances(1000, 0, 100)\n\tcheckAllowances(300, 0, 0, 0, 0, 0)\n\n\turequire.NoError(t, adm.SpendAllowance(alice, bob, 100))\n\tcheckBalances(1000, 0, 100)\n\tcheckAllowances(200, 0, 0, 0, 0, 0)\n}\n\nfunc TestOverflow(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tbob := testutils.TestAddress(\"bob\")\n\ttok, adm := NewToken(\"Dummy\", \"DUMMY\", 6)\n\n\turequire.NoError(t, adm.Mint(alice, 2\u003c\u003c62))\n\turequire.Equal(t, tok.BalanceOf(alice), uint64(2\u003c\u003c62))\n\turequire.Error(t, adm.Mint(bob, 2\u003c\u003c62))\n}\n"},{"name":"types.gno","body":"package grc20\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/grc/exts\"\n)\n\n// Teller interface defines the methods that a GRC20 token must implement. It\n// extends the TokenMetadata interface to include methods for managing token\n// transfers, allowances, and querying balances.\n//\n// The Teller interface is designed to ensure that any token adhering to this\n// standard provides a consistent API for interacting with fungible tokens.\ntype Teller interface {\n\texts.TokenMetadata\n\n\t// Returns the amount of tokens in existence.\n\tTotalSupply() uint64\n\n\t// Returns the amount of tokens owned by `account`.\n\tBalanceOf(account std.Address) uint64\n\n\t// Moves `amount` tokens from the caller's account to `to`.\n\t//\n\t// Returns an error if the operation failed.\n\tTransfer(to std.Address, amount uint64) error\n\n\t// Returns the remaining number of tokens that `spender` will be\n\t// allowed to spend on behalf of `owner` through {transferFrom}. This is\n\t// zero by default.\n\t//\n\t// This value changes when {approve} or {transferFrom} are called.\n\tAllowance(owner, spender std.Address) uint64\n\n\t// Sets `amount` as the allowance of `spender` over the caller's tokens.\n\t//\n\t// Returns an error if the operation failed.\n\t//\n\t// IMPORTANT: Beware that changing an allowance with this method brings\n\t// the risk that someone may use both the old and the new allowance by\n\t// unfortunate transaction ordering. One possible solution to mitigate\n\t// this race condition is to first reduce the spender's allowance to 0\n\t// and set the desired value afterwards:\n\t// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n\tApprove(spender std.Address, amount uint64) error\n\n\t// Moves `amount` tokens from `from` to `to` using the\n\t// allowance mechanism. `amount` is then deducted from the caller's\n\t// allowance.\n\t//\n\t// Returns an error if the operation failed.\n\tTransferFrom(from, to std.Address, amount uint64) error\n}\n\n// Token represents a fungible token with a name, symbol, and a certain number\n// of decimal places. It maintains a ledger for tracking balances and allowances\n// of addresses.\n//\n// The Token struct provides methods for retrieving token metadata, such as the\n// name, symbol, and decimals, as well as methods for interacting with the\n// ledger, including checking balances and allowances.\ntype Token struct {\n\t// Name of the token (e.g., \"Dummy Token\").\n\tname string\n\t// Symbol of the token (e.g., \"DUMMY\").\n\tsymbol string\n\t// Number of decimal places used for the token's precision.\n\tdecimals uint\n\t// Pointer to the PrivateLedger that manages balances and allowances.\n\tledger *PrivateLedger\n}\n\n// TokenGetter is a function type that returns a Token pointer. This type allows\n// bypassing a limitation where we cannot directly pass Token pointers between\n// realms. Instead, we pass this function which can then be called to get the\n// Token pointer. For more details on this limitation and workaround, see:\n// https://github.com/gnolang/gno/pull/3135\ntype TokenGetter func() *Token\n\n// PrivateLedger is a struct that holds the balances and allowances for the\n// token. It provides administrative functions for minting, burning,\n// transferring tokens, and managing allowances.\n//\n// The PrivateLedger is not safe to expose publicly, as it contains sensitive\n// information regarding token balances and allowances, and allows direct,\n// unrestricted access to all administrative functions.\ntype PrivateLedger struct {\n\t// Total supply of the token managed by this ledger.\n\ttotalSupply uint64\n\t// std.Address -\u003e uint64\n\tbalances avl.Tree\n\t// owner.(std.Address)+\":\"+spender.(std.Address)) -\u003e uint64\n\tallowances avl.Tree\n\t// Pointer to the associated Token struct\n\ttoken *Token\n}\n\nvar (\n\tErrInsufficientBalance = errors.New(\"insufficient balance\")\n\tErrInsufficientAllowance = errors.New(\"insufficient allowance\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrReadonly = errors.New(\"banker is readonly\")\n\tErrRestrictedTokenOwner = errors.New(\"restricted to bank owner\")\n\tErrOverflow = errors.New(\"Mint overflow\")\n)\n\nconst (\n\tMintEvent = \"Mint\"\n\tBurnEvent = \"Burn\"\n\tTransferEvent = \"Transfer\"\n\tApprovalEvent = \"Approval\"\n)\n\ntype fnTeller struct {\n\taccountFn func() std.Address\n\t*Token\n}\n\nvar _ Teller = (*fnTeller)(nil)\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"grc20reg","path":"gno.land/r/demo/grc20reg","files":[{"name":"grc20reg.gno","body":"package grc20reg\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/fqname\"\n\t\"gno.land/p/demo/grc/grc20\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar registry = avl.NewTree() // rlmPath[.slug] -\u003e TokenGetter (slug is optional)\n\nfunc Register(tokenGetter grc20.TokenGetter, slug string) {\n\trlmPath := std.PrevRealm().PkgPath()\n\tkey := fqname.Construct(rlmPath, slug)\n\tregistry.Set(key, tokenGetter)\n\tstd.Emit(\n\t\tregisterEvent,\n\t\t\"pkgpath\", rlmPath,\n\t\t\"slug\", slug,\n\t)\n}\n\nfunc Get(key string) grc20.TokenGetter {\n\ttokenGetter, ok := registry.Get(key)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn tokenGetter.(grc20.TokenGetter)\n}\n\nfunc MustGet(key string) grc20.TokenGetter {\n\ttokenGetter := Get(key)\n\tif tokenGetter == nil {\n\t\tpanic(\"unknown token: \" + key)\n\t}\n\treturn tokenGetter\n}\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\": // home\n\t\t// TODO: add pagination\n\t\ts := \"\"\n\t\tcount := 0\n\t\tregistry.Iterate(\"\", \"\", func(key string, tokenI interface{}) bool {\n\t\t\tcount++\n\t\t\ttokenGetter := tokenI.(grc20.TokenGetter)\n\t\t\ttoken := tokenGetter()\n\t\t\trlmPath, slug := fqname.Parse(key)\n\t\t\trlmLink := fqname.RenderLink(rlmPath, slug)\n\t\t\tinfoLink := \"/r/demo/grc20reg:\" + key\n\t\t\ts += ufmt.Sprintf(\"- **%s** - %s - [info](%s)\\n\", token.GetName(), rlmLink, infoLink)\n\t\t\treturn false\n\t\t})\n\t\tif count == 0 {\n\t\t\treturn \"No registered token.\"\n\t\t}\n\t\treturn s\n\tdefault: // specific token\n\t\tkey := path\n\t\ttokenGetter := MustGet(key)\n\t\ttoken := tokenGetter()\n\t\trlmPath, slug := fqname.Parse(key)\n\t\trlmLink := fqname.RenderLink(rlmPath, slug)\n\t\ts := ufmt.Sprintf(\"# %s\\n\", token.GetName())\n\t\ts += ufmt.Sprintf(\"- symbol: **%s**\\n\", token.GetSymbol())\n\t\ts += ufmt.Sprintf(\"- realm: %s\\n\", rlmLink)\n\t\ts += ufmt.Sprintf(\"- decimals: %d\\n\", token.GetDecimals())\n\t\ts += ufmt.Sprintf(\"- total supply: %d\\n\", token.TotalSupply())\n\t\treturn s\n\t}\n}\n\nconst registerEvent = \"register\"\n"},{"name":"grc20reg_test.gno","body":"package grc20reg\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc20\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestRegistry(t *testing.T) {\n\tstd.TestSetRealm(std.NewCodeRealm(\"gno.land/r/demo/foo\"))\n\trealmAddr := std.CurrentRealm().PkgPath()\n\ttoken, ledger := grc20.NewToken(\"TestToken\", \"TST\", 4)\n\tledger.Mint(std.CurrentRealm().Addr(), 1234567)\n\ttokenGetter := func() *grc20.Token { return token }\n\t// register\n\tRegister(tokenGetter, \"\")\n\tregTokenGetter := Get(realmAddr)\n\tregToken := regTokenGetter()\n\turequire.True(t, regToken != nil, \"expected to find a token\") // fixme: use urequire.NotNil\n\turequire.Equal(t, regToken.GetSymbol(), \"TST\")\n\n\texpected := `- **TestToken** - [gno.land/r/demo/foo](/r/demo/foo) - [info](/r/demo/grc20reg:gno.land/r/demo/foo)\n`\n\tgot := Render(\"\")\n\turequire.True(t, strings.Contains(got, expected))\n\t// 404\n\tinvalidToken := Get(\"0xdeadbeef\")\n\turequire.True(t, invalidToken == nil)\n\n\t// register with a slug\n\tRegister(tokenGetter, \"mySlug\")\n\tregTokenGetter = Get(realmAddr + \".mySlug\")\n\tregToken = regTokenGetter()\n\turequire.True(t, regToken != nil, \"expected to find a token\") // fixme: use urequire.NotNil\n\turequire.Equal(t, regToken.GetSymbol(), \"TST\")\n\n\t// override\n\tRegister(tokenGetter, \"\")\n\tregTokenGetter = Get(realmAddr + \"\")\n\tregToken = regTokenGetter()\n\turequire.True(t, regToken != nil, \"expected to find a token\") // fixme: use urequire.NotNil\n\turequire.Equal(t, regToken.GetSymbol(), \"TST\")\n\n\tgot = Render(\"\")\n\turequire.True(t, strings.Contains(got, `- **TestToken** - [gno.land/r/demo/foo](/r/demo/foo) - [info](/r/demo/grc20reg:gno.land/r/demo/foo)`))\n\turequire.True(t, strings.Contains(got, `- **TestToken** - [gno.land/r/demo/foo](/r/demo/foo).mySlug - [info](/r/demo/grc20reg:gno.land/r/demo/foo.mySlug)`))\n\n\texpected = `# TestToken\n- symbol: **TST**\n- realm: [gno.land/r/demo/foo](/r/demo/foo).mySlug\n- decimals: 4\n- total supply: 1234567\n`\n\tgot = Render(\"gno.land/r/demo/foo.mySlug\")\n\turequire.Equal(t, expected, got)\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"tests","path":"gno.land/r/demo/tests","files":[{"name":"README.md","body":"Modules here are only useful for file realm tests.\nThey can be safely ignored for other purposes.\n"},{"name":"interfaces.gno","body":"package tests\n\nimport (\n\t\"strconv\"\n)\n\ntype Stringer interface {\n\tString() string\n}\n\nvar stringers []Stringer\n\nfunc AddStringer(str Stringer) {\n\t// NOTE: this is ridiculous, a slice that will become too long\n\t// eventually. Don't do this in production programs; use\n\t// gno.land/p/demo/avl or similar structures.\n\tstringers = append(stringers, str)\n}\n\nfunc Render(path string) string {\n\tres := \"\"\n\t// NOTE: like the function above, this function too will eventually\n\t// become too expensive to call.\n\tfor i, stringer := range stringers {\n\t\tres += strconv.Itoa(i) + \": \" + stringer.String() + \"\\n\"\n\t}\n\treturn res\n}\n"},{"name":"nestedpkg_test.gno","body":"package tests\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\nfunc TestNestedPkg(t *testing.T) {\n\t// direct child\n\tcur := \"gno.land/r/demo/tests/foo\"\n\tstd.TestSetRealm(std.NewCodeRealm(cur))\n\tif !IsCallerSubPath() {\n\t\tt.Errorf(cur + \" should be a sub path\")\n\t}\n\tif IsCallerParentPath() {\n\t\tt.Errorf(cur + \" should not be a parent path\")\n\t}\n\tif !HasCallerSameNamespace() {\n\t\tt.Errorf(cur + \" should be from the same namespace\")\n\t}\n\n\t// grand-grand-child\n\tcur = \"gno.land/r/demo/tests/foo/bar/baz\"\n\tstd.TestSetRealm(std.NewCodeRealm(cur))\n\tif !IsCallerSubPath() {\n\t\tt.Errorf(cur + \" should be a sub path\")\n\t}\n\tif IsCallerParentPath() {\n\t\tt.Errorf(cur + \" should not be a parent path\")\n\t}\n\tif !HasCallerSameNamespace() {\n\t\tt.Errorf(cur + \" should be from the same namespace\")\n\t}\n\n\t// direct parent\n\tcur = \"gno.land/r/demo\"\n\tstd.TestSetRealm(std.NewCodeRealm(cur))\n\tif IsCallerSubPath() {\n\t\tt.Errorf(cur + \" should not be a sub path\")\n\t}\n\tif !IsCallerParentPath() {\n\t\tt.Errorf(cur + \" should be a parent path\")\n\t}\n\tif !HasCallerSameNamespace() {\n\t\tt.Errorf(cur + \" should be from the same namespace\")\n\t}\n\n\t// fake parent (prefix)\n\tcur = \"gno.land/r/dem\"\n\tstd.TestSetRealm(std.NewCodeRealm(cur))\n\tif IsCallerSubPath() {\n\t\tt.Errorf(cur + \" should not be a sub path\")\n\t}\n\tif IsCallerParentPath() {\n\t\tt.Errorf(cur + \" should not be a parent path\")\n\t}\n\tif HasCallerSameNamespace() {\n\t\tt.Errorf(cur + \" should not be from the same namespace\")\n\t}\n\n\t// different namespace\n\tcur = \"gno.land/r/foo\"\n\tstd.TestSetRealm(std.NewCodeRealm(cur))\n\tif IsCallerSubPath() {\n\t\tt.Errorf(cur + \" should not be a sub path\")\n\t}\n\tif IsCallerParentPath() {\n\t\tt.Errorf(cur + \" should not be a parent path\")\n\t}\n\tif HasCallerSameNamespace() {\n\t\tt.Errorf(cur + \" should not be from the same namespace\")\n\t}\n}\n"},{"name":"realm_compositelit.gno","body":"package tests\n\ntype (\n\tWord uint\n\tnat []Word\n)\n\nvar zero = \u0026Int{\n\tneg: true,\n\tabs: []Word{0},\n}\n\n// structLit\ntype Int struct {\n\tneg bool\n\tabs nat\n}\n\nfunc GetZeroType() nat {\n\ta := zero.abs\n\treturn a\n}\n"},{"name":"realm_method38d.gno","body":"package tests\n\nvar abs nat\n\nfunc (n nat) Add() nat {\n\treturn []Word{0}\n}\n\nfunc GetAbs() nat {\n\tabs = []Word{0}\n\n\treturn abs\n}\n\nfunc AbsAdd() nat {\n\trt := GetAbs().Add()\n\n\treturn rt\n}\n"},{"name":"tests.gno","body":"package tests\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/nestedpkg\"\n\trsubtests \"gno.land/r/demo/tests/subtests\"\n)\n\nvar counter int\n\nfunc IncCounter() {\n\tcounter++\n}\n\nfunc Counter() int {\n\treturn counter\n}\n\nfunc CurrentRealmPath() string {\n\treturn std.CurrentRealm().PkgPath()\n}\n\nvar initOrigCaller = std.GetOrigCaller()\n\nfunc InitOrigCaller() std.Address {\n\treturn initOrigCaller\n}\n\nfunc CallAssertOriginCall() {\n\tstd.AssertOriginCall()\n}\n\nfunc CallIsOriginCall() bool {\n\treturn std.IsOriginCall()\n}\n\nfunc CallSubtestsAssertOriginCall() {\n\trsubtests.CallAssertOriginCall()\n}\n\nfunc CallSubtestsIsOriginCall() bool {\n\treturn rsubtests.CallIsOriginCall()\n}\n\n//----------------------------------------\n// Test structure to ensure cross-realm modification is prevented.\n\ntype TestRealmObject struct {\n\tField string\n}\n\nvar TestRealmObjectValue TestRealmObject\n\nfunc ModifyTestRealmObject(t *TestRealmObject) {\n\tt.Field += \"_modified\"\n}\n\nfunc (t *TestRealmObject) Modify() {\n\tt.Field += \"_modified\"\n}\n\n//----------------------------------------\n// Test helpers to test a particular realm bug.\n\ntype TestNode struct {\n\tName string\n\tChild *TestNode\n}\n\nvar (\n\tgTestNode1 *TestNode\n\tgTestNode2 *TestNode\n\tgTestNode3 *TestNode\n)\n\nfunc InitTestNodes() {\n\tgTestNode1 = \u0026TestNode{Name: \"first\"}\n\tgTestNode2 = \u0026TestNode{Name: \"second\", Child: \u0026TestNode{Name: \"second's child\"}}\n}\n\nfunc ModTestNodes() {\n\ttmp := \u0026TestNode{}\n\ttmp.Child = gTestNode2.Child\n\tgTestNode3 = tmp // set to new-real\n\t// gTestNode1 = tmp.Child // set back to original is-real\n\tgTestNode3 = nil // delete.\n}\n\nfunc PrintTestNodes() {\n\tprintln(gTestNode2.Child.Name)\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc GetRSubtestsPrevRealm() std.Realm {\n\treturn rsubtests.GetPrevRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\n}\n\nfunc IsCallerSubPath() bool {\n\treturn nestedpkg.IsCallerSubPath()\n}\n\nfunc IsCallerParentPath() bool {\n\treturn nestedpkg.IsCallerParentPath()\n}\n\nfunc HasCallerSameNamespace() bool {\n\treturn nestedpkg.IsSameNamespace()\n}\n"},{"name":"tests_test.gno","body":"package tests\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\nfunc TestAssertOriginCall(t *testing.T) {\n\t// CallAssertOriginCall(): no panic\n\tCallAssertOriginCall()\n\tif !CallIsOriginCall() {\n\t\tt.Errorf(\"expected IsOriginCall=true but got false\")\n\t}\n\n\t// CallAssertOriginCall() from a block: panic\n\texpectedReason := \"invalid non-origin call\"\n\tfunc() {\n\t\tdefer func() {\n\t\t\tr := recover()\n\t\t\tif r == nil || r.(string) != expectedReason {\n\t\t\t\tt.Errorf(\"expected panic with '%v', got '%v'\", expectedReason, r)\n\t\t\t}\n\t\t}()\n\t\t// if called inside a function literal, this is no longer an origin call\n\t\t// because there's one additional frame (the function literal block).\n\t\tif CallIsOriginCall() {\n\t\t\tt.Errorf(\"expected IsOriginCall=false but got true\")\n\t\t}\n\t\tCallAssertOriginCall()\n\t}()\n\n\t// CallSubtestsAssertOriginCall(): panic\n\tdefer func() {\n\t\tr := recover()\n\t\tif r == nil || r.(string) != expectedReason {\n\t\t\tt.Errorf(\"expected panic with '%v', got '%v'\", expectedReason, r)\n\t\t}\n\t}()\n\tif CallSubtestsIsOriginCall() {\n\t\tt.Errorf(\"expected IsOriginCall=false but got true\")\n\t}\n\tCallSubtestsAssertOriginCall()\n}\n\nfunc TestPrevRealm(t *testing.T) {\n\tvar (\n\t\tuser1Addr = std.DerivePkgAddr(\"user1.gno\")\n\t\trTestsAddr = std.DerivePkgAddr(\"gno.land/r/demo/tests\")\n\t)\n\t// When a single realm in the frames, PrevRealm returns the user\n\tif addr := GetPrevRealm().Addr(); addr != user1Addr {\n\t\tt.Errorf(\"want GetPrevRealm().Addr==%s, got %s\", user1Addr, addr)\n\t}\n\t// When 2 or more realms in the frames, PrevRealm returns the second to last\n\tif addr := GetRSubtestsPrevRealm().Addr(); addr != rTestsAddr {\n\t\tt.Errorf(\"want GetRSubtestsPrevRealm().Addr==%s, got %s\", rTestsAddr, addr)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tprintln(\"tests.CallIsOriginCall:\", tests.CallIsOriginCall())\n\ttests.CallAssertOriginCall()\n\tprintln(\"tests.CallAssertOriginCall doesn't panic when called directly\")\n\n\t{\n\t\t// if called inside a block, this is no longer an origin call because\n\t\t// there's one additional frame (the block).\n\t\tprintln(\"tests.CallIsOriginCall:\", tests.CallIsOriginCall())\n\t\tdefer func() {\n\t\t\tr := recover()\n\t\t\tprintln(\"tests.AssertOriginCall panics if when called inside a function literal:\", r)\n\t\t}()\n\t\ttests.CallAssertOriginCall()\n\t}\n}\n\n// Output:\n// tests.CallIsOriginCall: true\n// tests.CallAssertOriginCall doesn't panic when called directly\n// tests.CallIsOriginCall: true\n// tests.AssertOriginCall panics if when called inside a function literal: undefined\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tprintln(tests.Counter())\n\ttests.IncCounter()\n\tprintln(tests.Counter())\n}\n\n// Output:\n// 0\n// 1\n"},{"name":"z2_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/tests\"\n)\n\n// When a single realm in the frames, PrevRealm returns the user\n// When 2 or more realms in the frames, PrevRealm returns the second to last\nfunc main() {\n\tvar (\n\t\teoa = testutils.TestAddress(\"someone\")\n\t\trTestsAddr = std.DerivePkgAddr(\"gno.land/r/demo/tests\")\n\t)\n\tstd.TestSetOrigCaller(eoa)\n\tprintln(\"tests.GetPrevRealm().Addr(): \", tests.GetPrevRealm().Addr())\n\tprintln(\"tests.GetRSubtestsPrevRealm().Addr(): \", tests.GetRSubtestsPrevRealm().Addr())\n}\n\n// Output:\n// tests.GetPrevRealm().Addr(): g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk\n// tests.GetRSubtestsPrevRealm().Addr(): g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq\n"},{"name":"z3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/test_test\npackage test_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tvar (\n\t\teoa = testutils.TestAddress(\"someone\")\n\t\trTestsAddr = std.DerivePkgAddr(\"gno.land/r/demo/tests\")\n\t)\n\tstd.TestSetOrigCaller(eoa)\n\t// Contrarily to z2_filetest.gno we EXPECT GetPrevRealms != eoa (#1704)\n\tif addr := tests.GetPrevRealm().Addr(); addr != eoa {\n\t\tprintln(\"want tests.GetPrevRealm().Addr ==\", eoa, \"got\", addr)\n\t}\n\t// When 2 or more realms in the frames, it is also different\n\tif addr := tests.GetRSubtestsPrevRealm().Addr(); addr != rTestsAddr {\n\t\tprintln(\"want GetRSubtestsPrevRealm().Addr ==\", rTestsAddr, \"got\", addr)\n\t}\n}\n\n// Output:\n// want tests.GetPrevRealm().Addr == g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk got g1xufrdvnfk6zc9r0nqa23ld3tt2r5gkyvw76q63\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"wugnot","path":"gno.land/r/demo/wugnot","files":[{"name":"wugnot.gno","body":"package wugnot\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/grc/grc20\"\n\t\"gno.land/p/demo/ufmt\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/demo/grc20reg\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar Token, adm = grc20.NewToken(\"wrapped GNOT\", \"wugnot\", 0)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc init() {\n\tgetter := func() *grc20.Token { return Token }\n\tgrc20reg.Register(getter, \"\")\n}\n\nfunc Deposit() {\n\tcaller := std.PrevRealm().Addr()\n\tsent := std.GetOrigSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\n\tcheckErr(adm.Mint(caller, uint64(amount)))\n}\n\nfunc Withdraw(amount uint64) {\n\trequire(amount \u003e= wugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d wugnot.\", amount, wugnotMinDeposit))\n\n\tcaller := std.PrevRealm().Addr()\n\tpkgaddr := std.CurrentRealm().Addr()\n\tcallerBal := Token.BalanceOf(caller)\n\trequire(amount \u003c= callerBal, ufmt.Sprintf(\"Insufficient balance: %d available, %d needed.\", callerBal, amount))\n\n\t// send swapped ugnots to qcaller\n\tstdBanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", int64(amount)}}\n\tstdBanker.SendCoins(pkgaddr, caller, send)\n\tcheckErr(adm.Burn(caller, amount))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\tc := len(parts)\n\n\tswitch {\n\tcase path == \"\":\n\t\treturn Token.RenderHome()\n\tcase c == 2 \u0026\u0026 parts[0] == \"balance\":\n\t\towner := std.Address(parts[1])\n\t\tbalance := Token.BalanceOf(owner)\n\t\treturn ufmt.Sprintf(\"%d\", balance)\n\tdefault:\n\t\treturn \"404\"\n\t}\n}\n\nfunc TotalSupply() uint64 { return Token.TotalSupply() }\n\nfunc BalanceOf(owner pusers.AddressOrName) uint64 {\n\townerAddr := users.Resolve(owner)\n\treturn Token.BalanceOf(ownerAddr)\n}\n\nfunc Allowance(owner, spender pusers.AddressOrName) uint64 {\n\townerAddr := users.Resolve(owner)\n\tspenderAddr := users.Resolve(spender)\n\treturn Token.Allowance(ownerAddr, spenderAddr)\n}\n\nfunc Transfer(to pusers.AddressOrName, amount uint64) {\n\ttoAddr := users.Resolve(to)\n\tuserTeller := Token.CallerTeller()\n\tcheckErr(userTeller.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tuserTeller := Token.CallerTeller()\n\tcheckErr(userTeller.Approve(spenderAddr, amount))\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, amount uint64) {\n\tfromAddr := users.Resolve(from)\n\ttoAddr := users.Resolve(to)\n\tuserTeller := Token.CallerTeller()\n\tcheckErr(userTeller.TransferFrom(fromAddr, toAddr, amount))\n}\n\nfunc require(condition bool, msg string) {\n\tif !condition {\n\t\tpanic(msg)\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/wugnot_test\npackage wugnot_test\n\nimport (\n\t\"fmt\"\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/wugnot\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\taddr1 = testutils.TestAddress(\"test1\")\n\taddrc = std.DerivePkgAddr(\"gno.land/r/demo/wugnot\")\n\taddrt = std.DerivePkgAddr(\"gno.land/r/demo/wugnot_test\")\n)\n\nfunc main() {\n\tstd.TestSetOrigPkgAddr(addrc)\n\tstd.TestIssueCoins(addrc, std.Coins{{\"ugnot\", 100000001}}) // TODO: remove this\n\n\t// issue ugnots\n\tstd.TestIssueCoins(addr1, std.Coins{{\"ugnot\", 100000001}})\n\n\t// print initial state\n\tprintBalances()\n\t// println(wugnot.Render(\"queues\"))\n\t// println(\"A -\", wugnot.Render(\"\"))\n\n\tstd.TestSetOrigCaller(addr1)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 123_400}}, nil)\n\twugnot.Deposit()\n\tprintBalances()\n\twugnot.Withdraw(4242)\n\tprintBalances()\n}\n\nfunc printBalances() {\n\tprintSingleBalance := func(name string, addr std.Address) {\n\t\twugnotBal := wugnot.BalanceOf(pusers.AddressOrName(addr))\n\t\tstd.TestSetOrigCaller(addr)\n\t\trobanker := std.GetBanker(std.BankerTypeReadonly)\n\t\tcoins := robanker.GetCoins(addr).AmountOf(\"ugnot\")\n\t\tfmt.Printf(\"| %-13s | addr=%s | wugnot=%-5d | ugnot=%-9d |\\n\",\n\t\t\tname, addr, wugnotBal, coins)\n\t}\n\tprintln(\"-----------\")\n\tprintSingleBalance(\"wugnot_test\", addrt)\n\tprintSingleBalance(\"wugnot\", addrc)\n\tprintSingleBalance(\"addr1\", addr1)\n\tprintln(\"-----------\")\n}\n\n// Output:\n// -----------\n// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=0 | ugnot=0 |\n// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=100000001 |\n// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 |\n// -----------\n// -----------\n// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=123400 | ugnot=0 |\n// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=100000001 |\n// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 |\n// -----------\n// -----------\n// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=119158 | ugnot=4242 |\n// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=99995759 |\n// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 |\n// -----------\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}}